Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> @override annotation. JSDocInfo jsDoc = getFunctionJsDocInfo(n); if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.isInterface() || jsDoc.hasThisType() || jsDoc.isOverride())) { return false; // Only traverse the right side if it's not an assignment to a prototype // property or subproperty. if (NodeUtil.isGet(lhs)) { if (lhs.getType() == Token.GETPROP && lhs.getLastChild().getString().equals("prototype")) { return false; } Node llhs = lhs.getFirstChild(); if (llhs.getType() == Token.GETPROP && llhs.getLastChild().getString().equals("prototype")) { return false; } } } } return true; } public void visit(NodeTraversal t, Node n, Node parent) { if (n.getType() == Token.THIS && shouldReportThis(n, parent)) { compiler.report(t.makeError(n, level, GLOBAL_THIS)); } if (n == assignLhsChild) { assignLhsChild = null; } } private boolean shouldReportThis(Node n, Node parent) { if (assignLhsChild != null) { // Always report a THIS on the left side of an assign. return true; } // Also report a THIS with a property access. return parent != null && NodeUtil.isGet(parent); } /** * Gets a function's JSDoc information, if it has any. Checks for a few * patterns (ellipses show where JSDoc would be): * <pre> * ... function() {} * ... x = function() {}; * var ... x = function() {}; * ... var x = function() {}; * </pre> */ private JSDocInfo getFunctionJsDocInfo(Node n) { JSDocInfo jsDoc = n.getJSDocInfo(); Node parent = n.getParent(); if (jsDoc == null) { int parentType = parent.getType(); if (parentType == Token.NAME || parentType == Token.ASSIGN) { jsDoc = parent.getJSDocInfo(); if (jsDoc == null && parentType == Token.NAME) { Node gramps = parent.getParent(); if (gramps.getType() == Token.VAR) { jsDoc = gramps.getJSDocInfo(); } } } } return jsDoc; } }

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> * * Note: jsscan.c uses C isspace() (which allows * \v, I think.) note that code in getChar() implicitly accepts * '\r' == \u000D as well. */ static boolean isJSSpace(int c) { if (c <= 127) { return c == 0x20 || c == 0x9 || c == 0xC || c == 0xB; } else { return c == 0xA0 || Character.getType((char)c) == Character.SPACE_SEPARATOR; } } private static boolean isJSFormatChar(int c) { return c > 127 && Character.getType((char)c) == Character.FORMAT; } /** * Gets the accumulated {@link JSDocInfo} and resets it. * Obsolete */ JSDocInfo getAndResetJSDocInfo() { return null; } /** * Returns any {@link JSDocInfo} with a fileoverview tag that showed up. * Obsolete */ JSDocInfo getFileOverviewJSDocInfo() { return null; } /** * Returns whether any {@link JSDocInfo} was accumulated. * Obsolete */ boolean isPopulated() { return false; } /** * Parser calls the method when it gets / or /= in literal context. */ void readRegExp(int startToken) throws IOException { stringBufferTop = 0; if (startToken == Token.ASSIGN_DIV) { // Miss-scanned /= addToString('='); } else { if (startToken != Token.DIV) Kit.codeBug(); } boolean inCharSet = false; // true if inside a '['..']' pair int c; while ((c = getChar()) != '/' || inCharSet) { if (c == '\n' || c == EOF_CHAR) { ungetChar(c); throw parser.reportError("msg.unterminated.re.lit"); } if (c == '\\') { addToString(c); c = getChar(); } else if (c == '[') { inCharSet = true; } else if (c == ']') { inCharSet = false; } addToString(c); } int reEnd = stringBufferTop; while (true) { if (matchChar('g')) addToString('g'); else if (matchChar('i')) addToString('i'); else if (matchChar('m')) addToString('m'); else break; } if (isAlpha(peekChar())) { throw parser.reportError("msg.invalid.re.flag"); } this.string = new String(stringBuffer, 0, reEnd); this.regExpFlags = new String(stringBuffer, reEnd, stringBufferTop - reEnd); } boolean isXMLAttribute() { return xmlIsAttribute; } int getFirstXMLToken() throws IOException { xmlOpenTagsCount = 0; xmlIsAttribute = false; xmlIsTagContent = false; ungetChar('<'); return getNextXMLToken(); }

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> n) { return n.getParent().getParent(); } /** * @return Whether the a line break should be added after the specified * BLOCK. */ @Override boolean breakAfterBlockFor(Node n, boolean isStatementContext) { Preconditions.checkState(n.getType() == Token.BLOCK); Node parent = n.getParent(); if (parent != null) { int type = parent.getType(); switch (type) { case Token.DO: // Don't break before 'while' in DO-WHILE statements. return false; case Token.FUNCTION: // FUNCTIONs are handled separately, don't break here. return false; case Token.TRY: // Don't break before catch return n != parent.getFirstChild(); case Token.CATCH: // Don't break before finally return !NodeUtil.hasFinally(getTryForCatch(parent)); case Token.IF: // Don't break before else return n == parent.getLastChild(); } } return true; } } static class CompactCodePrinter extends MappedCodePrinter { // The CompactCodePrinter tries to emit just enough newlines to stop there // being lines longer than the threshold. Since the output is going to be // gzipped, it makes sense to try to make the newlines appear in similar // contexts so that GZIP can encode them for 'free'. // // This version tries to break the lines at 'preferred' places, which are // between the top-level forms. This works because top level forms tend to // be more uniform than arbitary legal contexts. Better compression would // probably require explicit modelling of the gzip algorithm. private final boolean lineBreak; private int lineStartPosition = 0; private int preferredBreakPosition = 0; /** * @param lineBreak break the lines a bit more aggressively * @param lineLengthThreshold The length of a line after which we force * a newline when possible. * @param createSrcMap Whether to gather source position * mapping information when printing. * @param sourceMapDetailLevel A filter to control which nodes get mapped into * the source map. */ private CompactCodePrinter(boolean lineBreak, int lineLengthThreshold, boolean createSrcMap, SourceMap.DetailLevel sourceMapDetailLevel) { super(lineLengthThreshold, createSrcMap, sourceMapDetailLevel); this.lineBreak = lineBreak; } /** * Appends a string to the code, keeping track of the current line length. */ @Override void append(String str) { code.append(str); lineLength += str.length(); } /** * Adds a newline to the code, resetting the line length. */ @Override void startNewLine() { if (lineLength > 0) { code.append('\n'); lineLength = 0; lineIndex++; lineStartPosition = code.length(); } } @Override void maybeLineBreak() { if (lineBreak) { if (sawFunction) { startNewLine(); sawFunction = false; } } // Since we are at a legal line break, can we upgrade the // preferred break position? We prefer to break after a //

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> private static final Function<TypePair, TypePair> SHNE = new Function<TypePair, TypePair>() { public TypePair apply(TypePair p) { if (p.typeA == null || p.typeB == null) { return null; } return p.typeA.getTypesUnderShallowInequality(p.typeB); } }; /** * Merging function for inequality comparisons between types. */ private final Function<TypePair, TypePair> INEQ = new Function<TypePair, TypePair>() { public TypePair apply(TypePair p) { return new TypePair( getRestrictedWithoutUndefined(p.typeA), getRestrictedWithoutUndefined(p.typeB)); } }; /** * Creates a semantic reverse abstract interpreter. */ SemanticReverseAbstractInterpreter(CodingConvention convention, JSTypeRegistry typeRegistry) { super(convention, typeRegistry); } public FlowScope getPreciserScopeKnowingConditionOutcome(Node condition, FlowScope blindScope, boolean outcome) { // Check for the typeof operator. int operatorToken = condition.getType(); switch (operatorToken) { case Token.EQ: case Token.NE: case Token.SHEQ: case Token.SHNE: case Token.CASE: Node left; Node right; if (operatorToken == Token.CASE) { left = condition.getParent().getFirstChild(); // the switch condition right = condition.getFirstChild(); } else { left = condition.getFirstChild(); right = condition.getLastChild(); } Node typeOfNode = null; Node stringNode = null; if (left.getType() == Token.TYPEOF && right.getType() == Token.STRING) { typeOfNode = left; stringNode = right; } else if (right.getType() == Token.TYPEOF && left.getType() == Token.STRING) { typeOfNode = right; stringNode = left; } if (typeOfNode != null && stringNode != null) { Node operandNode = typeOfNode.getFirstChild(); JSType operandType = getTypeIfRefinable(operandNode, blindScope); if (operandType != null) { boolean resultEqualsValue = operatorToken == Token.EQ || operatorToken == Token.SHEQ || operatorToken == Token.CASE; if (!outcome) { resultEqualsValue = !resultEqualsValue; } return caseTypeOf(operandNode, operandType, stringNode.getString(), resultEqualsValue, blindScope); } } } switch (operatorToken) { case Token.AND: if (outcome) { return caseAndOrNotShortCircuiting(condition.getFirstChild(), condition.getLastChild(), blindScope, true); } else { return caseAndOrMaybeShortCircuiting(condition.getFirstChild(), condition.getLastChild(), blindScope, true); } case Token.OR: if (!outcome) { return caseAndOrNotShortCircuiting(condition.getFirstChild(), condition.getLastChild(), blindScope, false); } else { return caseAndOrMaybeShortCircuiting(condition.getFirstChild(), condition.getLastChild(), blindScope, false); } case Token.EQ: if (outcome) { return caseEquality(condition

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>, blindScope, EQ); } else { return caseEquality(condition, blindScope, NE); } case Token.NE: if (outcome) { return caseEquality(condition, blindScope, NE); } else { return caseEquality(condition, blindScope, EQ); } case Token.SHEQ: if (outcome) { return caseEquality(condition, blindScope, SHEQ); } else { return caseEquality(condition, blindScope, SHNE); } case Token.SHNE: if (outcome) { return caseEquality(condition, blindScope, SHNE); } else { return caseEquality(condition, blindScope, SHEQ); } case Token.NAME: case Token.GETPROP: return caseNameOrGetProp(condition, blindScope, outcome); case Token.ASSIGN: return firstPreciserScopeKnowingConditionOutcome( condition.getFirstChild(), firstPreciserScopeKnowingConditionOutcome( condition.getFirstChild().getNext(), blindScope, outcome), outcome); case Token.NOT: return firstPreciserScopeKnowingConditionOutcome( condition.getFirstChild(), blindScope, !outcome); case Token.LE: case Token.LT: case Token.GE: case Token.GT: if (outcome) { return caseEquality(condition, blindScope, INEQ); } break; case Token.INSTANCEOF: return caseInstanceOf( condition.getFirstChild(), condition.getLastChild(), blindScope, outcome); case Token.IN: if (outcome && condition.getFirstChild().getType() == Token.STRING) { return caseIn(condition.getLastChild(), condition.getFirstChild().getString(), blindScope); } break; case Token.CASE: Node left = condition.getParent().getFirstChild(); // the switch condition Node right = condition.getFirstChild(); if (outcome) { return caseEquality(left, right, blindScope, SHEQ); } else { return caseEquality(left, right, blindScope, SHNE); } } return nextPreciserScopeKnowingConditionOutcome( condition, blindScope, outcome); } private FlowScope caseEquality(Node condition, FlowScope blindScope, Function<TypePair, TypePair> merging) { return caseEquality(condition.getFirstChild(), condition.getLastChild(), blindScope, merging); } private FlowScope caseEquality(Node left, Node right, FlowScope blindScope, Function<TypePair, TypePair> merging) { // left type JSType leftType = getTypeIfRefinable(left, blindScope); boolean leftIsRefineable; if (leftType != null) { leftIsRefineable = true; } else { leftIsRefineable = false; leftType = left.getJSType(); } // right type JSType rightType = getTypeIfRefinable(right, blindScope); boolean rightIsRefineable; if (rightType != null) { rightIsRefineable = true; } else { rightIsRefineable = false; rightType = right.getJSType(); } // merged types TypePair merged = merging.apply(new TypePair(leftType, rightType));

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> <li>A</li> * <li>213</li> * <li>FOO_BAR</li> * </ul> */ @Override public boolean isValidEnumKey(String key) { return ENUM_KEY_PATTERN.matcher(key).matches(); } /** * {@inheritDoc} * * <p>In Google code, parameter names beginning with {@code opt_} are * treated as optional arguments. */ @Override public boolean isOptionalParameter(Node parameter) { return parameter.getString().startsWith(OPTIONAL_ARG_PREFIX); } @Override public boolean isVarArgsParameter(Node parameter) { return VAR_ARGS_NAME.equals(parameter.getString()); } /** * {@inheritDoc} * * <p>In Google code, any global name starting with an underscore is * considered exported. */ @Override public boolean isExported(String name, boolean local) { return !local && name.startsWith("_"); } /** * {@inheritDoc} * * <p>In Google code, private names end with an underscore, and exported * names are never considered private (see {@link #isExported}). */ @Override public boolean isPrivate(String name) { return name.endsWith("_") && !isExported(name); } }

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> Preconditions.checkState(child.getParent() == n); normalizeNodeTypes(child); } } /** * Add blocks to IF, WHILE, DO, etc. */ private void normalizeBlocks(Node n) { if (NodeUtil.isControlStructure(n) && n.getType() != Token.LABEL && n.getType() != Token.SWITCH) { for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (NodeUtil.isControlStructureCodeBlock(n,c) && c.getType() != Token.BLOCK) { Node newBlock = new Node(Token.BLOCK, n.getLineno(), n.getCharno()); newBlock.copyInformationFrom(n); n.replaceChild(c, newBlock); if (c.getType() != Token.EMPTY) { newBlock.addChildrenToFront(c); } else { newBlock.setWasEmptyNode(true); } c = newBlock; reportChange(); } } } } /** * Normalize where annotations appear on the AST. Copies * around existing JSDoc annotations as well as internal annotations. */ static class PrepareAnnotations extends NodeTraversal.AbstractPostOrderCallback { private final CodingConvention convention; PrepareAnnotations(AbstractCompiler compiler) { this.convention = compiler.getCodingConvention(); } public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.CALL: annotateCalls(n); break; case Token.FUNCTION: annotateFunctions(n, parent); annotateDispatchers(n, parent); break; case Token.NAME: case Token.STRING: annotateConstants(n, parent); break; case Token.OBJECTLIT: visitObjectLiteral(n); break; } } private void visitObjectLiteral(Node objlit) { Preconditions.checkState(objlit.getType() == Token.OBJECTLIT); for (Node key = objlit.getFirstChild(); key != null; key = key.getNext()) { Node value = key.getFirstChild(); visitObjectLiteralKey(objlit, key, value); } } /** * Prepare the object literal keys. */ private void visitObjectLiteralKey(Node objlit, Node key, Node value) { normalizeObjectLitJsDocs(objlit, key, value); annotateObjLitConstants(objlit, key, value); } /** * There are two types of calls we are interested in calls without explicit * "this" values (what we are call "free" calls) and direct call to eval. */ private void annotateCalls(Node n) { Preconditions.checkState(n.getType() == Token.CALL); // Keep track of of the "this" context of a call. A call without an // explicit "this" is a free call. Node first = n.getFirstChild(); if (!NodeUtil.isGet(first)) { n.putBooleanProp(Node.FREE_CALL, true); } // Keep track of the context in which eval is called. It is important // to distinguish between "(0, eval)()" and "eval()". if (first.getType() == Token.NAME &&

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> "eval".equals(first.getString())) { first.putBooleanProp(Node.DIRECT_EVAL, true); } } /** * Translate dispatcher info into the property expected node. */ private void annotateDispatchers(Node n, Node parent) { Preconditions.checkState(n.getType() == Token.FUNCTION); if (parent.getJSDocInfo() != null && parent.getJSDocInfo().isJavaDispatch()) { if (parent.getType() == Token.ASSIGN) { Preconditions.checkState(parent.getLastChild() == n); n.putBooleanProp(Node.IS_DISPATCHER, true); } } } /** * In the AST that Rhino gives us, it needs to make a distinction * between jsdoc on the object literal node and jsdoc on the object literal * value. For example, * <pre> * var x = { * / JSDOC / * a: 'b', * c: / JSDOC / 'd' * }; * </pre> * * But in few narrow cases (in particular, function literals), it's * a lot easier for us if the doc is attached to the value. */ private void normalizeObjectLitJsDocs(Node objlit, Node key, Node value) { Preconditions.checkState(objlit.getType() == Token.OBJECTLIT); if (key.getJSDocInfo() != null && value.getType() == Token.FUNCTION) { value.setJSDocInfo(key.getJSDocInfo()); } } /** * Mark names and properties that are constants by convention. */ private void annotateConstants(Node n, Node parent) { Preconditions.checkState( n.getType() == Token.NAME || n.getType() == Token.STRING); // There are only two cases where a string token // may be a variable reference: The right side of a GETPROP // or an OBJECTLIT key. if (n.getType() != Token.STRING || parent.getType() == Token.GETPROP) { if (NodeUtil.isConstantByConvention(convention, n, parent)) { n.putBooleanProp(Node.IS_CONSTANT_NAME, true); } } } /** * Mark objlit names that are constants by convention. */ private void annotateObjLitConstants(Node objlit, Node key, Node value) { if (key.getType() == Token.NAME || key.getType() == Token.STRING) { String name = key.getString(); if (convention.isConstantKey(name)) { key.putBooleanProp(Node.IS_CONSTANT_NAME, true); } } } /** * Annotate optional and var_arg function parameters. */ private void annotateFunctions(Node n, Node parent) { JSDocInfo fnInfo = NodeUtil.getFunctionInfo(n); // Compute which function parameters are optional and // which are var_args. Node args = n.getFirstChild().getNext(); for (Node arg = args.getFirstChild(); arg != null; arg = arg.getNext()) { String argName = arg.getString(); JSTypeExpression typeExpr = fnInfo == null ? null : fnInfo.getParameterType(arg

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>, JSType ownerType) { if (!overridingType.canAssignTo(hiddenType)) { registerMismatch(overridingType, hiddenType); if (shouldReport) { compiler.report( t.makeError(n, HIDDEN_PROPERTY_MISMATCH, propertyName, ownerType.toString(), hiddenType.toString(), overridingType.toString())); } } } /** * Expect that the first type is the direct superclass of the second type. * * @param t The node traversal. * @param n The node where warnings should point to. * @param superObject The expected super instance type. * @param subObject The sub instance type. */ void expectSuperType(NodeTraversal t, Node n, ObjectType superObject, ObjectType subObject) { FunctionType subCtor = subObject.getConstructor(); ObjectType declaredSuper = subObject.getImplicitPrototype().getImplicitPrototype(); if (!declaredSuper.equals(superObject)) { if (declaredSuper.equals(getNativeType(OBJECT_TYPE))) { if (shouldReport) { compiler.report( t.makeError(n, MISSING_EXTENDS_TAG_WARNING, subObject.toString())); } registerMismatch(superObject, declaredSuper); } else { mismatch(t.getSourceName(), n, "mismatch in declaration of superclass type", superObject, declaredSuper); } // Correct the super type. if (!subCtor.hasCachedValues()) { subCtor.setPrototypeBasedOn(superObject); } } } /** * Expect that the first type can be cast to the second type. The first type * should be either a subtype or supertype of the second. * * @param t The node traversal. * @param n The node where warnings should point. * @param type The type being cast from. * @param castType The type being cast to. */ void expectCanCast(NodeTraversal t, Node n, JSType type, JSType castType) { castType = castType.restrictByNotNullOrUndefined(); type = type.restrictByNotNullOrUndefined(); if (!type.canAssignTo(castType) && !castType.canAssignTo(type)) { if (shouldReport) { compiler.report( t.makeError(n, INVALID_CAST, castType.toString(), type.toString())); } registerMismatch(type, castType); } } /** * Expect that the given variable has not been declared with a type. * * @param sourceName The name of the source file we're in. * @param n The node where warnings should point to. * @param parent The parent of {@code n}. * @param var The variable that we're checking. * @param variableName The name of the variable. * @param newType The type being applied to the variable. Mostly just here * for the benefit of the warning. */ void expectUndeclaredVariable(String sourceName, Node n, Node parent, Var var, String variableName, JSType newType) { boolean allowDupe = false; if (n.getType() == Token.GETPROP) { JSDocInfo info = n.getJSDocInfo(); if (info == null)

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> { info = parent.getJSDocInfo(); } allowDupe = info != null && info.getSuppressions().contains("duplicate"); } JSType varType = var.getType(); // Only report duplicate declarations that have types. Other duplicates // will be reported by the syntactic scope creator later in the // compilation process. if (varType != null && varType != typeRegistry.getNativeType(UNKNOWN_TYPE) && newType != null && newType != typeRegistry.getNativeType(UNKNOWN_TYPE)) { // If there are two typed declarations of the same variable, that // is an error and the second declaration is ignored, except in the // case of native types. A null input type means that the declaration // was made in TypedScopeCreator#createInitialScope and is a // native type. if (var.input == null) { n.setJSType(varType); if (parent.getType() == Token.VAR) { if (n.getFirstChild() != null) { n.getFirstChild().setJSType(varType); } } else { Preconditions.checkState(parent.getType() == Token.FUNCTION); parent.setJSType(varType); } } else { // Always warn about duplicates if the overridden type does not // match the original type. // // If the types match, suppress the warning iff there was a @suppress // tag, or if the original declaration was a stub. if (!(allowDupe || var.getParentNode().getType() == Token.EXPR_RESULT) || !newType.equals(varType)) { if (shouldReport) { compiler.report( JSError.make(sourceName, n, DUP_VAR_DECLARATION, variableName, newType.toString(), var.getInputName(), String.valueOf(var.nameNode.getLineno()), varType.toString())); } } } } } /** * Expect that all properties on interfaces that this type implements are * implemented. */ void expectAllInterfacePropertiesImplemented(FunctionType type) { ObjectType instance = type.getInstanceType(); for (ObjectType implemented : type.getAllImplementedInterfaces()) { if (implemented.getImplicitPrototype() != null) { for (String prop : implemented.getImplicitPrototype().getOwnPropertyNames()) { if (!instance.hasProperty(prop)) { Node source = type.getSource(); Preconditions.checkNotNull(source); String sourceName = (String) source.getProp(Node.SOURCENAME_PROP); sourceName = sourceName == null ? "" : sourceName; if (shouldReport) { compiler.report(JSError.make(sourceName, source, INTERFACE_METHOD_NOT_IMPLEMENTED, prop, implemented.toString(), instance.toString())); } registerMismatch(instance, implemented); } } } } } /** * Report a type mismatch */ private void mismatch(NodeTraversal t, Node n, String msg, JSType found, JSType required) { mismatch(t.getSourceName(), n, msg, found, required); } private void mismatch(NodeTraversal t, Node n, String msg, JSType found, JSTypeNative required) { mismatch(t, n, msg, found,

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> private Map<String, DefineInfo> collectDefines(Node root, GlobalNamespace namespace) { // Find all the global names with a @define annotation List<Name> allDefines = Lists.newArrayList(); for (Name name : namespace.getNameIndex().values()) { if (name.docInfo != null && name.docInfo.isDefine()) { // Process defines should not depend on check types being enabled, // so we look for the JSDoc instead of the inferred type. if (isValidDefineType(name.docInfo.getType())) { allDefines.add(name); } else { JSError error = JSError.make( name.declaration.sourceName, name.declaration.node, INVALID_DEFINE_TYPE_ERROR); compiler.report(error); } } else if (name.refs != null) { for (Ref ref : name.refs) { Node n = ref.node; Node parent = ref.node.getParent(); JSDocInfo info = n.getJSDocInfo(); if (info == null && parent.getType() == Token.VAR && parent.hasOneChild()) { info = parent.getJSDocInfo(); } if (info != null && info.isDefine()) { allDefines.add(name); break; } } } } CollectDefines pass = new CollectDefines(compiler, allDefines); NodeTraversal.traverse(compiler, root, pass); return pass.getAllDefines(); } /** * Finds all assignments to @defines, and figures out the last value of * the @define. */ private static final class CollectDefines implements Callback { private final AbstractCompiler compiler; private final Map<String, DefineInfo> assignableDefines; private final Map<String, DefineInfo> allDefines; private final Map<Node, RefInfo> allRefInfo; // A hack that allows us to remove ASSIGN/VAR statements when // we're currently visiting one of the children of the assign. private Node lvalueToRemoveLater = null; // A stack tied to the node traversal, to keep track of whether // we're in a conditional block. If 1 is at the top, assignment to // a define is allowed. Otherwise, it's not allowed. private final Deque<Integer> assignAllowed; CollectDefines(AbstractCompiler compiler, List<Name> listOfDefines) { this.compiler = compiler; this.allDefines = Maps.newHashMap(); assignableDefines = Maps.newHashMap(); assignAllowed = new ArrayDeque<Integer>(); assignAllowed.push(1); // Create a map of references to defines keyed by node for easy lookup allRefInfo = Maps.newHashMap(); for (Name name : listOfDefines) { if (name.declaration != null) { allRefInfo.put(name.declaration.node, new RefInfo(name.declaration, name)); } if (name.refs != null) { for (Ref ref : name.refs) { // If there's a TWIN def, only put one of the twins in. if (ref.getTwin() == null || !ref.getTwin().isSet()) { allRefInfo.put(ref.node, new RefInfo(ref, name)); } } } } } /**

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> * Get a map of {@link DefineInfo} structures, keyed by the name of * the define. */ Map<String, DefineInfo> getAllDefines() { return allDefines; } /** * Keeps track of whether the traversal is in a conditional branch. * We traverse all nodes of the parse tree. */ public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { updateAssignAllowedStack(n, true); return true; } public void visit(NodeTraversal t, Node n, Node parent) { RefInfo refInfo = allRefInfo.get(n); if (refInfo != null) { Ref ref = refInfo.ref; Name name = refInfo.name; String fullName = name.fullName(); switch (ref.type) { case SET_FROM_GLOBAL: case SET_FROM_LOCAL: Node valParent = getValueParent(ref); Node val = valParent.getLastChild(); if (valParent.getType() == Token.ASSIGN && name.isSimpleName() && name.declaration == ref) { // For defines, it's an error if a simple name is assigned // before it's declared compiler.report( t.makeError(val, INVALID_DEFINE_INIT_ERROR, fullName)); } else if (processDefineAssignment(t, fullName, val, valParent)) { // remove the assignment so that the variable is still declared, // but no longer assigned to a value, e.g., // DEF_FOO = 5; // becomes "5;" // We can't remove the ASSIGN/VAR when we're still visiting its // children, so we'll have to come back later to remove it. refInfo.name.removeRef(ref); lvalueToRemoveLater = valParent; } break; default: if (t.inGlobalScope()) { // Treat this as a reference to a define in the global scope. // After this point, the define must not be reassigned, // or it's an error. DefineInfo info = assignableDefines.get(fullName); if (info != null) { setDefineInfoNotAssignable(info, t); assignableDefines.remove(fullName); } } break; } } if (!t.inGlobalScope() && n.getJSDocInfo() != null && n.getJSDocInfo().isDefine()) { // warn about @define annotations in local scopes compiler.report( t.makeError(n, NON_GLOBAL_DEFINE_INIT_ERROR, "")); } if (lvalueToRemoveLater == n) { lvalueToRemoveLater = null; if (n.getType() == Token.ASSIGN) { Node last = n.getLastChild(); n.removeChild(last); parent.replaceChild(n, last); } else { Preconditions.checkState(n.getType() == Token.NAME); n.removeChild(n.getFirstChild()); } compiler.reportCodeChange(); } if (n.getType() == Token.CALL) { if (t.inGlobalScope()) { // If there's a function call in the global scope, // we just say it's unsafe and freeze all the defines. // // NOTE(nicksantos): We

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> could be a lot smarter here. For example, // ReplaceOverriddenVars keeps a call graph of all functions and // which functions/variables that they reference, and tries // to statically determine which functions are "safe" and which // are not. But this would be overkill, expecially because // the intended use of defines is with config_files, where // all the defines are at the top of the bundle. for (DefineInfo info : assignableDefines.values()) { setDefineInfoNotAssignable(info, t); } assignableDefines.clear(); } } updateAssignAllowedStack(n, false); } /** * Determines whether assignment to a define should be allowed * in the subtree of the given node, and if not, records that fact. * * @param n The node whose subtree we're about to enter or exit. * @param entering True if we're entering the subtree, false otherwise. */ private void updateAssignAllowedStack(Node n, boolean entering) { switch (n.getType()) { case Token.CASE: case Token.FOR: case Token.FUNCTION: case Token.HOOK: case Token.IF: case Token.SWITCH: case Token.WHILE: if (entering) { assignAllowed.push(0); } else { assignAllowed.remove(); } break; } } /** * Determines whether assignment to a define should be allowed * at the current point of the traversal. */ private boolean isAssignAllowed() { return assignAllowed.element() == 1; } /** * Tracks the given define. * * @param t The current traversal, for context. * @param name The full name for this define. * @param value The value assigned to the define. * @param valueParent The parent node of value. * @return Whether we should remove this assignment from the parse tree. */ private boolean processDefineAssignment(NodeTraversal t, String name, Node value, Node valueParent) { if (value == null || !NodeUtil.isValidDefineValue(value, allDefines.keySet())) { compiler.report( t.makeError(value, INVALID_DEFINE_INIT_ERROR, name)); } else if (!isAssignAllowed()) { compiler.report( t.makeError(valueParent, NON_GLOBAL_DEFINE_INIT_ERROR, name)); } else { DefineInfo info = allDefines.get(name); if (info == null) { // First declaration of this define. info = new DefineInfo(value, valueParent); allDefines.put(name, info); assignableDefines.put(name, info); } else if (info.recordAssignment(value)) { // The define was already initialized, but this is a safe // re-assignment. return true; } else { // The define was already initialized, and this is an unsafe // re-assignment. compiler.report( t.makeError(valueParent, DEFINE_NOT_ASSIGNABLE_ERROR, name, info.getReasonWhyNotAssignable())); } } return false; } /** * Gets the parent node of the value for any assignment to a Name. * For example, in the assignment * {@code var x = 3

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>;} * the parent would be the NAME node. */ private static Node getValueParent(Ref ref) { // there are two types of declarations: VARs and ASSIGNs return ref.node.getParent() != null && ref.node.getParent().getType() == Token.VAR ? ref.node : ref.node.getParent(); } /** * Records the fact that because of the current node in the node traversal, * the define can't ever be assigned again. * * @param info Represents the define variable. * @param t The current traversal. */ private void setDefineInfoNotAssignable(DefineInfo info, NodeTraversal t) { info.setNotAssignable(format(REASON_DEFINE_NOT_ASSIGNABLE, t.getLineNumber(), t.getSourceName())); } /** * A simple data structure for associating a Ref with the name * that it references. */ private static class RefInfo { final Ref ref; final Name name; RefInfo(Ref ref, Name name) { this.ref = ref; this.name = name; } } } /** * A simple class for storing information about a define. * Gathers the initial value, the last assigned value, and whether * the define can be safely assigned a new value. */ private static final class DefineInfo { public final Node initialValueParent; public final Node initialValue; private Node lastValue; private boolean isAssignable; private String reasonNotAssignable; /** * Initializes a define. */ public DefineInfo(Node initialValue, Node initialValueParent) { this.initialValueParent = initialValueParent; this.initialValue = initialValue; lastValue = initialValue; isAssignable = true; } /** * Records the fact that this define can't be assigned a value anymore. * * @param reason A message describing the reason why it can't be assigned. */ public void setNotAssignable(String reason) { isAssignable = false; reasonNotAssignable = reason; } /** * Gets the reason why a define is not assignable. */ public String getReasonWhyNotAssignable() { return reasonNotAssignable; } /** * Records an assigned value. * * @return False if there was an error. */ public boolean recordAssignment(Node value) { lastValue = value; return isAssignable; } /** * Gets the last assigned value. */ public Node getLastValue() { return lastValue; } } }

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>Traversal nodeTraversal, Node n, Node parent); /** * <p>Visits a node in post order (after its children have been visited). * A node is visited only if all its parents should be traversed * ({@link #shouldTraverse(NodeTraversal, Node, Node)}).</p> * <p>Implementations can have side effects (e.g. modifying the parse * tree).</p> */ void visit(NodeTraversal t, Node n, Node parent); } /** * Callback that also knows about scope changes */ public interface ScopedCallback extends Callback { /** * Called immediately after entering a new scope. The new scope can * be accessed through t.getScope() */ void enterScope(NodeTraversal t); /** * Called immediately before exiting a scope. The ending scope can * be accessed through t.getScope() */ void exitScope(NodeTraversal t); } /** * Abstract callback to visit all nodes in post order. */ public abstract static class AbstractPostOrderCallback implements Callback { public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { return true; } } /** * Abstract callback to visit all nodes but not traverse into function * bodies. */ public abstract static class AbstractShallowCallback implements Callback { public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { // We do want to traverse the name of a named function, but we don't // want to traverse the arguments or body. return parent == null || parent.getType() != Token.FUNCTION || n == parent.getFirstChild(); } } /** * Abstract callback to visit all structure and statement nodes but doesn't * traverse into functions or expressions. */ public abstract static class AbstractShallowStatementCallback implements Callback { public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { return parent == null || NodeUtil.isControlStructure(parent) || NodeUtil.isStatementBlock(parent); } } /** * Abstract callback to visit a pruned set of nodes. */ public abstract static class AbstractNodeTypePruningCallback implements Callback { private final Set<Integer> nodeTypes; private final boolean include; /** * Creates an abstract pruned callback. * @param nodeTypes the nodes to include in the traversal */ public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes) { this(nodeTypes, true); } /** * Creates an abstract pruned callback. * @param nodeTypes the nodes to include/exclude in the traversal * @param include whether to include or exclude the nodes in the traversal */ public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes, boolean include) { this.nodeTypes = nodeTypes; this.include = include; } public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { return include == nodeTypes.contains(n.getType()); } } /** * Creates a node traversal using the specified callback interface. */ public NodeTraversal(AbstractCompiler compiler, Callback cb) { this(compiler, cb, new SyntacticScopeCreator(compiler)); } /** * Creates a node traversal using the specified callback interface

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> * and the scope creator. */ public NodeTraversal(AbstractCompiler compiler, Callback cb, ScopeCreator scopeCreator) { this.callback = cb; if (cb instanceof ScopedCallback) { this.scopeCallback = (ScopedCallback) cb; } this.compiler = compiler; this.sourceName = ""; this.scopeCreator = scopeCreator; } private void throwUnexpectedException(Exception unexpectedException) { // If there's an unexpected exception, try to get the // line number of the code that caused it. String message = unexpectedException.getMessage(); // TODO(user): It is possible to get more information if curNode or // its parent is missing. We still have the scope stack in which it is still // very useful to find out at least which function caused the exception. if (!sourceName.isEmpty()) { message = unexpectedException.getMessage() + "\n" + formatNodeContext("Node", curNode) + (curNode == null ? "" : formatNodeContext("Parent", curNode.getParent())); } compiler.throwInternalError(message, unexpectedException); } private String formatNodeContext(String label, Node n) { if (n == null) { return " " + label + ": NULL"; } return " " + label + "(" + n.toString(false, false, false) + "): " + formatNodePosition(n); } /** * Traverses a parse tree recursively. */ public void traverse(Node root) { try { sourceName = ""; curNode = root; pushScope(root); traverseBranch(root, null); popScope(); } catch (Exception unexpectedException) { throwUnexpectedException(unexpectedException); } } public void traverseRoots(Node ... roots) { traverseRoots(Lists.newArrayList(roots)); } public void traverseRoots(List<Node> roots) { if (roots.isEmpty()) { return; } try { Node scopeRoot = roots.get(0).getParent(); Preconditions.checkState(scopeRoot != null); sourceName = ""; curNode = scopeRoot; pushScope(scopeRoot); for (Node root : roots) { Preconditions.checkState(root.getParent() == scopeRoot); traverseBranch(root, scopeRoot); } popScope(); } catch (Exception unexpectedException) { throwUnexpectedException(unexpectedException); } } private static final String MISSING_SOURCE = "[source unknown]"; private String formatNodePosition(Node n) { if (n == null) { return MISSING_SOURCE + "\n"; } int lineNumber = n.getLineno(); int columnNumber = n.getCharno(); String src = compiler.getSourceLine(sourceName, lineNumber); if (src == null) { src = MISSING_SOURCE; } return sourceName + ":" + lineNumber + ":" + columnNumber + "\n" + src + "\n"; } /** * Traverses a parse tree recursively with a scope, starting with the given * root. This should only be used in the global scope. Otherwise, use * {@link #traverseAtScope}. */ void traverseWithScope(Node root, Scope s) { Preconditions.checkState(s.is

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>Global()); sourceName = ""; curNode = root; pushScope(s); traverseBranch(root, null); popScope(); } /** * Traverses a parse tree recursively with a scope, starting at that scope's * root. */ void traverseAtScope(Scope s) { Node n = s.getRootNode(); if (n.getType() == Token.FUNCTION) { // We need to do some extra magic to make sure that the scope doesn't // get re-created when we dive into the function. sourceName = getSourceName(n); curNode = n; pushScope(s); Node args = n.getFirstChild().getNext(); Node body = args.getNext(); traverseBranch(args, n); traverseBranch(body, n); popScope(); } else { traverseWithScope(n, s); } } /** * Traverses an inner node recursively with a refined scope. An inner node may * be any node with a non {@code null} parent (i.e. all nodes except the * root). * * @param node the node to traverse * @param parent the node's parent, it may be not be {@code null} * @param refinedScope the refined scope of the scope currently at the top of * the scope stack or in trivial cases that very scope or {@code null} */ protected void traverseInnerNode(Node node, Node parent, Scope refinedScope) { Preconditions.checkNotNull(parent); if (refinedScope != null && getScope() != refinedScope) { curNode = node; pushScope(refinedScope); traverseBranch(node, parent); popScope(); } else { traverseBranch(node, parent); } } /** * Gets the compiler. */ public Compiler getCompiler() { // TODO(nicksantos): Remove this type cast. This is just temporary // while refactoring. return (Compiler) compiler; } /** * Gets the current line number, or zero if it cannot be determined. The line * number is retrieved lazily as a running time optimization. */ public int getLineNumber() { Node cur = curNode; while (cur != null) { int line = cur.getLineno(); if (line >=0) { return line; } cur = cur.getParent(); } return 0; } /** * Gets the current input source name. * * @return A string that may be empty, but not null */ public String getSourceName() { return sourceName; } /** * Gets the current input source. */ public CompilerInput getInput() { return compiler.getInput(sourceName); } /** * Gets the current input module. */ public JSModule getModule() { CompilerInput input = getInput(); return input == null ? null : input.getModule(); } /** Returns the node currently being traversed. */ public Node getCurrentNode() { return curNode; } /** * Traverses a node recursively. */ public static void traverse( AbstractCompiler compiler, Node root, Callback cb) { NodeTraversal t = new NodeTraversal(compiler, cb); t.traverse(root);

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> } /** * Traverses a list of node trees. */ public static void traverseRoots( AbstractCompiler compiler, List<Node> roots, Callback cb) { NodeTraversal t = new NodeTraversal(compiler, cb); t.traverseRoots(roots); } /** * Traverses a branch. */ @SuppressWarnings("fallthrough") private void traverseBranch(Node n, Node parent) { int type = n.getType(); if (type == Token.SCRIPT) { sourceName = getSourceName(n); } curNode = n; if (!callback.shouldTraverse(this, n, parent)) return; switch (type) { case Token.FUNCTION: traverseFunction(n, parent); break; default: for (Node child = n.getFirstChild(); child != null; ) { // child could be replaced, in which case our child node // would no longer point to the true next Node next = child.getNext(); traverseBranch(child, n); child = next; } break; } curNode = n; callback.visit(this, n, parent); } /** * Traverses a function. */ private void traverseFunction(Node n, Node parent) { Preconditions.checkState(n.getChildCount() == 3); Preconditions.checkState(n.getType() == Token.FUNCTION); final Node fnName = n.getFirstChild(); boolean isFunctionExpression = (parent != null) && NodeUtil.isFunctionExpression(n); if (!isFunctionExpression) { // Functions declarations are in the scope containing the declaration. traverseBranch(fnName, n); } curNode = n; pushScope(n); if (isFunctionExpression) { // Function expression names are only accessible within the function // scope. traverseBranch(fnName, n); } final Node args = fnName.getNext(); final Node body = args.getNext(); // Args traverseBranch(args, n); // Body Preconditions.checkState(body.getNext() == null && body.getType() == Token.BLOCK); traverseBranch(body, n); popScope(); } /** Examines the functions stack for the last instance of a function node. */ @SuppressWarnings("unchecked") public Node getEnclosingFunction() { if (scopes.size() + scopeRoots.size() < 2) { return null; } else { if (scopeRoots.isEmpty()) { return scopes.peek().getRootNode(); } else { return scopeRoots.peek(); } } } /** Creates a new scope (e.g. when entering a function). */ private void pushScope(Node node) { Preconditions.checkState(curNode != null); scopeRoots.push(node); cfgs.push(null); if (scopeCallback != null) { scopeCallback.enterScope(this); } } /** Creates a new scope (e.g. when entering a function). */ private void pushScope(Scope s) { Preconditions.checkState(curNode != null); scopes.push(s); cfgs.push(null); if (scopeCallback != null) { scopeCallback.enterScope(this); } } /** Pops back to

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>/* * Copyright 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Predicate; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.io.IOException; /** * Collects information mapping the generated (compiled) source back to * its original source for debugging purposes. * * @see CodeConsumer * @see CodeGenerator * @see CodePrinter * */ public interface SourceMap { enum Format { LEGACY { @Override SourceMap getInstance() { return new SourceMapLegacy(); } }, EXPERIMENTIAL { @Override SourceMap getInstance() { return new SourceMap2(); } }; abstract SourceMap getInstance(); } /** * Source maps can be very large different levels of detail can be specified. */ public enum DetailLevel implements Predicate<Node> { // ALL is best when the fullest details are needed for debugging or for // code-origin analysis. ALL { @Override public boolean apply(Node node) { return true; } }, // SYMBOLS is intended to be used for stack trace deobfuscation when full // detail is not needed. SYMBOLS { @Override public boolean apply(Node node) { return NodeUtil.isCall(node) || NodeUtil.isNew(node) || NodeUtil.isFunction(node) || NodeUtil.isName(node) || NodeUtil.isGet(node) || (NodeUtil.isString(node) && (node.getParent().getType() == Token.OBJECTLIT || NodeUtil.isGet(node.getParent()))); } }; } /** * Appends the source map to the given buffer. * * @param out The stream to which the map will be appended. * @param name The name of the generated source file that this source map * represents. */ void appendTo(Appendable out, String name) throws IOException; /** * Resets the source map for reuse. A reset needs to be called between * each generated output file. */ void reset(); /** * Adds a mapping for the given node. Mappings must be added in order. * * @param node The node that the new mapping represents. * @param startPosition The position on the starting line * @param endPosition The position on the ending line. */ void addMapping(Node node, Position startPosition, Position endPosition); /** * Sets the prefix used for wrapping the generated source file before * it is written. This ensures that the source map is adjusted for the * change in character offsets.

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> AbstractCompiler compiler; // Whether this is the post-processing sanity check. private final boolean sanityCheck; // Whether extern checks emit error. private boolean strictExternCheck; VarCheck(AbstractCompiler compiler) { this(compiler, false); } VarCheck(AbstractCompiler compiler, boolean sanityCheck) { this.compiler = compiler; this.strictExternCheck = compiler.getErrorLevel( JSError.make("", 0, 0, UNDEFINED_EXTERN_VAR_ERROR)) == CheckLevel.ERROR; this.sanityCheck = sanityCheck; } @Override public void process(Node externs, Node root) { // Don't run externs-checking in sanity check mode. Normalization will // remove duplicate VAR declarations, which will make // externs look like they have assigns. if (!sanityCheck) { NodeTraversal.traverse(compiler, externs, new NameRefInExternsCheck()); } NodeTraversal.traverseRoots( compiler, Lists.newArrayList(externs, root), this); for (String varName : varsToDeclareInExterns) { createSynthesizedExternVar(varName); } } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.getType() != Token.NAME) { return; } String varName = n.getString(); // Only a function can have an empty name. if (varName.isEmpty()) { Preconditions.checkState(NodeUtil.isFunction(parent)); // A function declaration with an empty name passes Rhino, // but is supposed to be a syntax error according to the spec. if (!NodeUtil.isFunctionExpression(parent)) { t.report(n, INVALID_FUNCTION_DECL); } return; } // Check if this is a declaration for a var that has been declared // elsewhere. If so, mark it as a duplicate. if ((parent.getType() == Token.VAR || NodeUtil.isFunctionDeclaration(parent)) && varsToDeclareInExterns.contains(varName)) { createSynthesizedExternVar(varName); parent.addSuppression("duplicate"); } // Check that the var has been declared. Scope scope = t.getScope(); Scope.Var var = scope.getVar(varName); if (var == null) { if (NodeUtil.isFunctionExpression(parent)) { // e.g. [ function foo() {} ], it's okay if "foo" isn't defined in the // current scope. } else { // The extern checks are stricter, don't report a second error. if (!strictExternCheck || !t.getInput().isExtern()) { t.report(n, UNDEFINED_VAR_ERROR, varName); } if (sanityCheck) { throw new IllegalStateException("Unexpected variable " + varName); } else { createSynthesizedExternVar(varName); scope.getGlobalScope().declare(varName, n, null, getSynthesizedExternsInput()); } } return; } CompilerInput currInput = t.getInput(); CompilerInput varInput = var.input; if (currInput == varInput || currInput == null || varInput == null) { // The variable was defined in the same file. This

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> if (index < references.size() && index > 0) { Reference maybeDecl = references.get(index-1); if (maybeDecl.isVarDeclaration()) { Preconditions.checkState(!maybeDecl.isInitializingDeclaration()); Reference maybeInit = references.get(index); if (maybeInit.isSimpleAssignmentToName()) { return true; } } } return false; } /** * @return The reference that provides the value for the variable at the * time of the first read, if known, otherwise null. * * This is either the variable declaration ("var a = ...") or first * reference following the declaration if it is an assignment. */ Reference getInitializingReference() { if (isInitializingDeclarationAt(0)) { return references.get(0); } else if (isInitializingAssignmentAt(1)) { return references.get(1); } return null; } /** * Constants are allowed to be defined after their first use. */ Reference getInitializingReferenceForConstants() { int size = references.size(); for (int i = 0; i < size; i++) { if (isInitializingDeclarationAt(i) || isInitializingAssignmentAt(i)) { return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { break; } else if (block.isLoop) { return false; } } return true; } /** * @return The one and only assignment. Returns if there are 0 or 2+ * assignments. */ private Reference getOneAndOnlyAssignment() { Reference assignment = null; int size = references.size(); for (int i = 0; i < size; i++) { Reference ref = references.get(i); if (ref.isLvalue() || ref.isInitializingDeclaration()) { if (assignment == null) { assignment = ref; } else { return null; } } } return assignment; } /** * @return Whether the variable is never assigned a value. */ boolean isNeverAssigned() { int size = references.size(); for (int i = 0; i < size; i++) { Reference ref = references.get(i); if (ref.isLvalue() || ref.isInitializingDeclaration()) { return false; } } return true; } boolean firstReferenceIsAssigningDeclaration() { int size = references.size(); if (size > 0 && references.get(0).isInitializingDeclaration()) { return true; } return false; } } /** * Represents a single declaration or reference to a variable. */ static final class Reference { private static final Set<Integer> DECLARATION_PARENTS = ImmutableSet.of(Token.VAR,

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> Token.FUNCTION, Token.CATCH); private final Node nameNode; private final Node parent; private final Node grandparent; private final BasicBlock basicBlock; private final Scope scope; private final String sourceName; Reference(Node nameNode, Node parent, NodeTraversal t, BasicBlock basicBlock) { this(nameNode, parent, parent.getParent(), basicBlock, t.getScope(), t.getSourceName()); } // Bleeding functions are weird, because the declaration does // not appear inside their scope. So they need their own constructor. static Reference newBleedingFunction(NodeTraversal t, BasicBlock basicBlock, Node func) { return new Reference(func.getFirstChild(), func, func.getParent(), basicBlock, t.getScope(), t.getSourceName()); } private Reference(Node nameNode, Node parent, Node grandparent, BasicBlock basicBlock, Scope scope, String sourceName) { this.nameNode = nameNode; this.parent = parent; this.grandparent = grandparent; this.basicBlock = basicBlock; this.scope = scope; this.sourceName = sourceName; } boolean isDeclaration() { return DECLARATION_PARENTS.contains(parent.getType()) || parent.getType() == Token.LP && grandparent.getType() == Token.FUNCTION; } boolean isVarDeclaration() { return parent.getType() == Token.VAR; } boolean isHoistedFunction() { return NodeUtil.isHoistedFunctionDeclaration(parent); } /** * Determines whether the variable is initialized at the declaration. */ boolean isInitializingDeclaration() { // VAR is the only type of variable declaration that may not initialize // its variable. Catch blocks, named functions, and parameters all do. return isDeclaration() && (parent.getType() != Token.VAR || nameNode.getFirstChild() != null); } /** * @return For an assignment, variable declaration, or function declaration * return the assigned value, otherwise null. */ Node getAssignedValue() { return (parent.getType() == Token.FUNCTION) ? parent : NodeUtil.getAssignedValue(getNameNode()); } BasicBlock getBasicBlock() { return basicBlock; } Node getParent() { return parent; } Node getNameNode() { return nameNode; } Node getGrandparent() { return grandparent; } private static boolean isLhsOfForInExpression(Node n) { Node parent = n.getParent(); if (parent.getType() == Token.VAR) { return isLhsOfForInExpression(parent); } return NodeUtil.isForIn(parent) && parent.getFirstChild() == n; } boolean isSimpleAssignmentToName() { return parent.getType() == Token.ASSIGN && parent.getFirstChild() == nameNode; } boolean isLvalue() { int parentType = parent.getType(); return (parentType == Token.VAR && nameNode.getFirstChild() != null) || parentType == Token.INC || parentType == Token.DEC || (NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == nameNode) || isLhsOfForInExpression(nameNode); }

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> Scope getScope() { return scope; } public String getSourceName() { return sourceName; } } /** * Represents a section of code that is uninterrupted by control structures * (conditional or iterative logic). */ static final class BasicBlock { private final BasicBlock parent; /** * Determines whether the block may not be part of the normal control flow, * but instead "hoisted" to the top of the scope. */ private final boolean isHoisted; /** * Whether this block denotes a function scope. */ private final boolean isFunction; /** * Whether this block denotes a loop. */ private final boolean isLoop; /** * Creates a new block. * @param parent The containing block. * @param root The root node of the block. */ BasicBlock(BasicBlock parent, Node root) { this.parent = parent; // only named functions may be hoisted. this.isHoisted = NodeUtil.isHoistedFunctionDeclaration(root); this.isFunction = root.getType() == Token.FUNCTION; if (root.getParent() != null) { int pType = root.getParent().getType(); this.isLoop = pType == Token.DO || pType == Token.WHILE || pType == Token.FOR; } else { this.isLoop = false; } } BasicBlock getParent() { return parent; } /** * Determines whether this block is guaranteed to begin executing before * the given block does. */ boolean provablyExecutesBefore(BasicBlock thatBlock) { // If thatBlock is a descendant of this block, and there are no hoisted // blocks between them, then this block must start before thatBlock. BasicBlock currentBlock; for (currentBlock = thatBlock; currentBlock != null && currentBlock != this; currentBlock = currentBlock.getParent()) { if (currentBlock.isHoisted) { return false; } } return currentBlock == this; } } }

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> externsAndJs = jsRoot.getParent(); Preconditions.checkState(externsAndJs != null); Preconditions.checkState( externsRoot == null || externsAndJs.hasChild(externsRoot)); if (externsRoot != null) { check(externsRoot, true); } check(jsRoot, false); } /** Main entry point of this phase for testing code. */ public Scope processForTesting(Node externsRoot, Node jsRoot) { Preconditions.checkState(scopeCreator == null); Preconditions.checkState(topScope == null); Preconditions.checkState(jsRoot.getParent() != null); Node externsAndJsRoot = jsRoot.getParent(); scopeCreator = new MemoizedScopeCreator(new TypedScopeCreator(compiler)); topScope = scopeCreator.createScope(externsAndJsRoot, null); TypeInferencePass inference = new TypeInferencePass(compiler, reverseInterpreter, topScope, scopeCreator); inference.process(externsRoot, jsRoot); process(externsRoot, jsRoot); return topScope; } public void check(Node node, boolean externs) { Preconditions.checkNotNull(node); NodeTraversal t = new NodeTraversal(compiler, this, scopeCreator); inExterns = externs; t.traverseWithScope(node, topScope); if (externs) { inferJSDocInfo.process(node, null); } else { inferJSDocInfo.process(null, node); } } private void checkNoTypeCheckSection(Node n, boolean enterSection) { switch (n.getType()) { case Token.SCRIPT: case Token.BLOCK: case Token.VAR: case Token.FUNCTION: case Token.ASSIGN: JSDocInfo info = n.getJSDocInfo(); if (info != null && info.isNoTypeCheck()) { if (enterSection) { noTypeCheckSection++; } else { noTypeCheckSection--; } } validator.setShouldReport(noTypeCheckSection == 0); break; } } private void report(NodeTraversal t, Node n, DiagnosticType diagnosticType, String... arguments) { if (noTypeCheckSection == 0) { t.report(n, diagnosticType, arguments); } } public boolean shouldTraverse( NodeTraversal t, Node n, Node parent) { checkNoTypeCheckSection(n, true); switch (n.getType()) { case Token.FUNCTION: // normal type checking final TypeCheck outerThis = this; final Scope outerScope = t.getScope(); final FunctionType functionType = (FunctionType) n.getJSType(); final String functionPrivateName = n.getFirstChild().getString(); if (functionPrivateName != null && functionPrivateName.length() > 0 && outerScope.isDeclared(functionPrivateName, false) && // Ideally, we would want to check whether the type in the scope // differs from the type being defined, but then the extern // redeclarations of built-in types generates spurious warnings. !(outerScope.getVar( functionPrivateName).getType() instanceof FunctionType)) { report(t, n, FUNCTION_MASKS_VARIABLE, functionPrivateName);

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> } // TODO(user): Only traverse the function's body. The function's // name and arguments are traversed by the scope creator, and ideally // should not be traversed by the type checker. break; } return true; } /** * This is the meat of the type checking. It is basically one big switch, * with each case representing one type of parse tree node. The individual * cases are usually pretty straightforward. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. * @param parent The parent of the node n. */ public void visit(NodeTraversal t, Node n, Node parent) { JSType childType; JSType leftType, rightType; Node left, right; // To be explicitly set to false if the node is not typeable. boolean typeable = true; switch (n.getType()) { case Token.NAME: typeable = visitName(t, n, parent); break; case Token.LP: // If this is under a FUNCTION node, it is a parameter list and can be // ignored here. if (parent.getType() != Token.FUNCTION) { ensureTyped(t, n, getJSType(n.getFirstChild())); } else { typeable = false; } break; case Token.COMMA: ensureTyped(t, n, getJSType(n.getLastChild())); break; case Token.TRUE: case Token.FALSE: ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.THIS: ensureTyped(t, n, t.getScope().getTypeOfThis()); break; case Token.REF_SPECIAL: ensureTyped(t, n); break; case Token.GET_REF: ensureTyped(t, n, getJSType(n.getFirstChild())); break; case Token.NULL: ensureTyped(t, n, NULL_TYPE); break; case Token.NUMBER: if (n.getParent().getType() != Token.OBJECTLIT) { ensureTyped(t, n, NUMBER_TYPE); } else { typeable = false; } break; case Token.ARRAYLIT: ensureTyped(t, n, ARRAY_TYPE); break; case Token.STRING: if (n.getParent().getType() != Token.OBJECTLIT) { ensureTyped(t, n, STRING_TYPE); } else { typeable = false; } break; case Token.REGEXP: ensureTyped(t, n, REGEXP_TYPE); break; case Token.GETPROP: visitGetProp(t, n, parent); typeable = !(parent.getType() == Token.ASSIGN && parent.getFirstChild() == n); break; case Token.GETELEM: visitGetElem(t, n); // The type of GETELEM is always unknown, so no point counting that. // If that unknown leaks elsewhere (say by an assignment to another // variable), then it will be counted. typeable = false; break; case Token.VAR: visitVar(t, n); type

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>able = false; break; case Token.NEW: visitNew(t, n); typeable = true; break; case Token.CALL: visitCall(t, n); typeable = !NodeUtil.isExpressionNode(parent); break; case Token.RETURN: visitReturn(t, n); typeable = false; break; case Token.DEC: case Token.INC: left = n.getFirstChild(); validator.expectNumber( t, left, getJSType(left), "increment/decrement"); ensureTyped(t, n, NUMBER_TYPE); break; case Token.NOT: ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.VOID: ensureTyped(t, n, VOID_TYPE); break; case Token.TYPEOF: ensureTyped(t, n, STRING_TYPE); break; case Token.BITNOT: childType = getJSType(n.getFirstChild()); if (!childType.matchesInt32Context()) { report(t, n, BIT_OPERATION, NodeUtil.opToStr(n.getType()), childType.toString()); } ensureTyped(t, n, NUMBER_TYPE); break; case Token.POS: case Token.NEG: left = n.getFirstChild(); validator.expectNumber(t, left, getJSType(left), "sign operator"); ensureTyped(t, n, NUMBER_TYPE); break; case Token.EQ: case Token.NE: { leftType = getJSType(n.getFirstChild()); rightType = getJSType(n.getLastChild()); JSType leftTypeRestricted = leftType.restrictByNotNullOrUndefined(); JSType rightTypeRestricted = rightType.restrictByNotNullOrUndefined(); TernaryValue result = leftTypeRestricted.testForEquality(rightTypeRestricted); if (result != TernaryValue.UNKNOWN) { if (n.getType() == Token.NE) { result = result.not(); } report(t, n, DETERMINISTIC_TEST, leftType.toString(), rightType.toString(), result.toString()); } ensureTyped(t, n, BOOLEAN_TYPE); break; } case Token.SHEQ: case Token.SHNE: { leftType = getJSType(n.getFirstChild()); rightType = getJSType(n.getLastChild()); JSType leftTypeRestricted = leftType.restrictByNotNullOrUndefined(); JSType rightTypeRestricted = rightType.restrictByNotNullOrUndefined(); if (!leftTypeRestricted.canTestForShallowEqualityWith( rightTypeRestricted)) { report(t, n, DETERMINISTIC_TEST_NO_RESULT, leftType.toString(), rightType.toString()); } ensureTyped(t, n, BOOLEAN_TYPE); break; } case Token.LT: case Token.LE: case Token.GT: case Token.GE: leftType = getJSType(n.getFirstChild()); rightType = getJSType(n.getLastChild()); if (rightType.isNumber()) { validator.expectNumber( t, n, leftType, "left side of numeric comparison"); } else if (leftType.isNumber())

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> = getJSType(child); validator.expectObject( t, child, childType, "with requires an object"); typeable = false; break; } case Token.FUNCTION: visitFunction(t, n); break; // These nodes have no interesting type behavior. case Token.LABEL: case Token.LABEL_NAME: case Token.SWITCH: case Token.BREAK: case Token.CATCH: case Token.TRY: case Token.SCRIPT: case Token.EXPR_RESULT: case Token.BLOCK: case Token.EMPTY: case Token.DEFAULT: case Token.CONTINUE: case Token.DEBUGGER: case Token.THROW: typeable = false; break; // These nodes require data flow analysis. case Token.DO: case Token.FOR: case Token.IF: case Token.WHILE: typeable = false; break; // These nodes are typed during the type inference. case Token.AND: case Token.HOOK: case Token.OBJECTLIT: case Token.OR: if (n.getJSType() != null) { // If we didn't run type inference. ensureTyped(t, n); } else { // If this is an enum, then give that type to the objectlit as well. if ((n.getType() == Token.OBJECTLIT) && (parent.getJSType() instanceof EnumType)) { ensureTyped(t, n, parent.getJSType()); } else { ensureTyped(t, n); } } break; default: report(t, n, UNEXPECTED_TOKEN, Token.name(n.getType())); ensureTyped(t, n); break; } // Don't count externs since the user's code may not even use that part. typeable = typeable && !inExterns; if (typeable) { doPercentTypedAccounting(t, n); } checkNoTypeCheckSection(n, false); } /** * Counts the given node in the typed statistics. * @param n a node that should be typed */ private void doPercentTypedAccounting(NodeTraversal t, Node n) { JSType type = n.getJSType(); if (type == null) { nullCount++; } else if (type.isUnknownType()) { if (reportUnknownTypes.isOn()) { compiler.report( t.makeError(n, reportUnknownTypes, UNKNOWN_EXPR_TYPE)); } unknownCount++; } else { typedCount++; } } /** * Visits an assignment <code>lvalue = rvalue</code>. If the * <code>lvalue</code> is a prototype modification, we change the schema * of the object type it is referring to. * @param t the traversal * @param assign the assign node * (<code>assign.getType() == Token.ASSIGN</code> is an implicit invariant) */ private void visitAssign(NodeTraversal t, Node assign) { JSDocInfo info = assign.getJSDocInfo(); Node lvalue = assign.getFirstChild(); Node rvalue = assign.getLastChild(); if (lvalue.getType() == Token.GETPROP) { Node object =

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> lvalue.getFirstChild(); JSType objectJsType = getJSType(object); String property = lvalue.getLastChild().getString(); // the first name in this getprop refers to an interface // we perform checks in addition to the ones below if (object.getType() == Token.GETPROP) { JSType jsType = getJSType(object.getFirstChild()); if (jsType.isInterface() && object.getLastChild().getString().equals("prototype")) { visitInterfaceGetprop(t, assign, object, property, lvalue, rvalue); } } // /** @type ... */object.name = ...; if (info != null && info.hasType()) { visitAnnotatedAssignGetprop(t, assign, info.getType().evaluate(t.getScope(), typeRegistry), object, property, rvalue); return; } // /** @enum ... */object.name = ...; if (info != null && info.hasEnumParameterType()) { checkEnumInitializer( t, rvalue, info.getEnumParameterType().evaluate( t.getScope(), typeRegistry)); return; } // object.prototype = ...; if (property.equals("prototype")) { if (objectJsType instanceof FunctionType) { FunctionType functionType = (FunctionType) objectJsType; if (functionType.isConstructor()) { JSType rvalueType = rvalue.getJSType(); validator.expectObject(t, rvalue, rvalueType, OVERRIDING_PROTOTYPE_WITH_NON_OBJECT); } } else { // TODO(user): might want to flag that } return; } // object.prototype.property = ...; if (object.getType() == Token.GETPROP) { Node object2 = object.getFirstChild(); String property2 = NodeUtil.getStringValue(object.getLastChild()); if ("prototype".equals(property2)) { JSType jsType = object2.getJSType(); if (jsType instanceof FunctionType) { FunctionType functionType = (FunctionType) jsType; if (functionType.isConstructor() || functionType.isInterface()) { checkDeclaredPropertyInheritance( t, assign, functionType, property, info, getJSType(rvalue)); } } else { // TODO(user): might want to flag that } return; } } // object.property = ...; ObjectType type = ObjectType.cast( objectJsType.restrictByNotNullOrUndefined()); if (type != null) { if (type.hasProperty(property) && !type.isPropertyTypeInferred(property) && !propertyIsImplicitCast(type, property)) { validator.expectCanAssignToPropertyOf( t, assign, getJSType(rvalue), type.getPropertyType(property), object, property); } return; } } else if (lvalue.getType() == Token.NAME) { // variable with inferred type case JSType rvalueType = getJSType(assign.getLastChild()); Var var = t.getScope().getVar(lvalue.getString()); if (var != null) { if (var.isTypeInferred()) { return; } } } // fall through case JSType leftType =

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> getJSType(lvalue); Node rightChild = assign.getLastChild(); JSType rightType = getJSType(rightChild); if (validator.expectCanAssignTo( t, assign, rightType, leftType, "assignment")) { ensureTyped(t, assign, rightType); } else { ensureTyped(t, assign); } } /** * Returns true if any type in the chain has an implictCast annotation for * the given property. */ private boolean propertyIsImplicitCast(ObjectType type, String prop) { for (; type != null; type = type.getImplicitPrototype()) { JSDocInfo docInfo = type.getOwnPropertyJSDocInfo(prop); if (docInfo != null && docInfo.isImplicitCast()) { return true; } } return false; } /** * Given a constructor type and a property name, check that the property has * the JSDoc annotation @override iff the property is declared on a * superclass. Several checks regarding inheritance correctness are also * performed. */ private void checkDeclaredPropertyInheritance( NodeTraversal t, Node n, FunctionType ctorType, String propertyName, JSDocInfo info, JSType propertyType) { // TODO(user): We're not 100% confident that type-checking works, // so we return quietly if the unknown type is a superclass of this type. // Remove this check as we become more confident. We should flag a warning // when the unknown type is on the inheritance chain, as it is likely // because of a programmer error. if (ctorType.hasUnknownSupertype()) { return; } FunctionType superClass = ctorType.getSuperClassConstructor(); boolean superClassHasProperty = superClass != null && superClass.getPrototype().hasProperty(propertyName); boolean declaredOverride = info != null && info.isOverride(); boolean foundInterfaceProperty = false; if (ctorType.isConstructor()) { for (JSType implementedInterface : ctorType.getImplementedInterfaces()) { if (implementedInterface.isUnknownType()) { continue; } FunctionType interfaceType = implementedInterface.toObjectType().getConstructor(); boolean interfaceHasProperty = interfaceType.getPrototype().hasProperty(propertyName); foundInterfaceProperty = foundInterfaceProperty || interfaceHasProperty; if (reportMissingOverride.isOn() && !declaredOverride && interfaceHasProperty) { // @override not present, but the property does override an interface // property compiler.report(t.makeError(n, reportMissingOverride, HIDDEN_INTERFACE_PROPERTY, propertyName, interfaceType.getTopMostDefiningType(propertyName).toString())); } if (!declaredOverride) { continue; } // @override is present and we have to check that it is ok if (interfaceHasProperty) { JSType interfacePropType = interfaceType.getPrototype().getPropertyType(propertyName); if (!propertyType.canAssignTo(interfacePropType)) { compiler.report(t.makeError(n, HIDDEN_INTERFACE_PROPERTY_MISMATCH, propertyName, interfaceType.getTopMostDefiningType(propertyName).toString(), interfacePropType.toString(), propertyType.toString())); } } } } if (!declaredOverride && !superClassHasProperty

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>) { // nothing to do here, it's just a plain new property return; } JSType topInstanceType = superClassHasProperty ? superClass.getTopMostDefiningType(propertyName) : null; if (reportMissingOverride.isOn() && ctorType.isConstructor() && !declaredOverride && superClassHasProperty) { // @override not present, but the property does override a superclass // property compiler.report(t.makeError(n, reportMissingOverride, HIDDEN_SUPERCLASS_PROPERTY, propertyName, topInstanceType.toString())); } if (!declaredOverride) { // there's no @override to check return; } // @override is present and we have to check that it is ok if (superClassHasProperty) { // there is a superclass implementation JSType superClassPropType = superClass.getPrototype().getPropertyType(propertyName); if (!propertyType.canAssignTo(superClassPropType)) { compiler.report( t.makeError(n, HIDDEN_SUPERCLASS_PROPERTY_MISMATCH, propertyName, topInstanceType.toString(), superClassPropType.toString(), propertyType.toString())); } } else if (!foundInterfaceProperty) { // there is no superclass nor interface implementation compiler.report( t.makeError(n, UNKNOWN_OVERRIDE, propertyName, ctorType.getInstanceType().toString())); } } /** * Visits an ASSIGN node for cases such as * <pre> * interface.property2.property = ...; * </pre> */ private void visitInterfaceGetprop(NodeTraversal t, Node assign, Node object, String property, Node lvalue, Node rvalue) { JSType rvalueType = getJSType(rvalue); // Only 2 values are allowed for methods: // goog.abstractMethod // function () {}; // or for properties, no assignment such as: // InterfaceFoo.prototype.foobar; String abstractMethodName = compiler.getCodingConvention().getAbstractMethodName(); if (!rvalueType.isOrdinaryFunction() && !(rvalue.isQualifiedName() && rvalue.getQualifiedName().equals(abstractMethodName))) { // This is bad i18n style but we don't localize our compiler errors. String abstractMethodMessage = (abstractMethodName != null) ? ", or " + abstractMethodName : ""; compiler.report( t.makeError(object, INVALID_INTERFACE_MEMBER_DECLARATION, abstractMethodMessage)); } if (assign.getLastChild().getType() == Token.FUNCTION && !NodeUtil.isEmptyBlock(assign.getLastChild().getLastChild())) { compiler.report( t.makeError(object, INTERFACE_FUNCTION_NOT_EMPTY, abstractMethodName)); } } /** * Visits an ASSIGN node for cases such as * <pre> * object.property = ...; * </pre> * that have an {@code @type} annotation. */ private void visitAnnotatedAssignGetprop(NodeTraversal t, Node assign, JSType type, Node object, String property, Node rvalue) { // verifying that the rvalue has the correct type validator.expectCanAssignToPropertyOf(t, assign, getJSType(rvalue), type, object, property);

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>(); if (objectType != null) { JSType propType = getJSType(n); if ((!objectType.hasProperty(propName) || objectType.equals(typeRegistry.getNativeType(UNKNOWN_TYPE))) && propType.equals(typeRegistry.getNativeType(UNKNOWN_TYPE))) { if (objectType instanceof EnumType) { report(t, n, INEXISTENT_ENUM_ELEMENT, propName); } else if (!objectType.isEmptyType() && reportMissingProperties && !isPropertyTest(n)) { if (!typeRegistry.canPropertyBeDefined(objectType, propName)) { report(t, n, INEXISTENT_PROPERTY, propName, validator.getReadableJSTypeName(n.getFirstChild(), true)); } } } } else { // TODO(nicksantos): might want to flag the access on a non object when // it's impossible to get a property from this type. } } /** * Determines whether this node is testing for the existence of a property. * If true, we will not emit warnings about a missing property. * * @param getProp The GETPROP being tested. */ private boolean isPropertyTest(Node getProp) { Node parent = getProp.getParent(); switch (parent.getType()) { case Token.CALL: return parent.getFirstChild() != getProp && compiler.getCodingConvention().isPropertyTestFunction(parent); case Token.IF: case Token.WHILE: case Token.DO: case Token.FOR: return NodeUtil.getConditionExpression(parent) == getProp; case Token.INSTANCEOF: case Token.TYPEOF: return true; case Token.AND: case Token.HOOK: return parent.getFirstChild() == getProp; case Token.NOT: return parent.getParent().getType() == Token.OR && parent.getParent().getFirstChild() == parent; } return false; } /** * Visits a GETELEM node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. */ private void visitGetElem(NodeTraversal t, Node n) { Node left = n.getFirstChild(); Node right = n.getLastChild(); validator.expectIndexMatch(t, n, getJSType(left), getJSType(right)); ensureTyped(t, n); } /** * Visits a VAR node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. */ private void visitVar(NodeTraversal t, Node n) { // TODO(nicksantos): Fix this so that the doc info always shows up // on the NAME node. We probably want to wait for the parser // merge to fix this. JSDocInfo varInfo = n.hasOneChild() ? n.getJSDocInfo() : null; for (Node name : n.children()) { Node value = name.getFirstChild(); // A null var would indicate a bug in the scope creation

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> logic. Var var = t.getScope().getVar(name.getString()); if (value != null) { JSType valueType = getJSType(value); JSType nameType = var.getType(); nameType = (nameType == null) ? getNativeType(UNKNOWN_TYPE) : nameType; JSDocInfo info = name.getJSDocInfo(); if (info == null) { info = varInfo; } if (info != null && info.hasEnumParameterType()) { // var.getType() can never be null, this would indicate a bug in the // scope creation logic. checkEnumInitializer( t, value, info.getEnumParameterType().evaluate(t.getScope(), typeRegistry)); } else if (var.isTypeInferred()) { ensureTyped(t, name, valueType); } else { validator.expectCanAssignTo( t, value, valueType, nameType, "initializing variable"); } } } } /** * Visits a NEW node. */ private void visitNew(NodeTraversal t, Node n) { Node constructor = n.getFirstChild(); FunctionType type = getFunctionType(constructor); if (type != null && type.isConstructor()) { visitParameterList(t, n, type); ensureTyped(t, n, type.getInstanceType()); } else { // TODO(user): add support for namespaced objects. if (constructor.getType() != Token.GETPROP) { // TODO(user): make the constructor node have lineno/charno // and use constructor for a more precise error indication. // It seems that GETPROP nodes are missing this information. Node line; if (constructor.getLineno() < 0 || constructor.getCharno() < 0) { line = n; } else { line = constructor; } report(t, line, NOT_A_CONSTRUCTOR); } ensureTyped(t, n); } } /** * Visits a {@link Token#FUNCTION} node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. */ private void visitFunction(NodeTraversal t, Node n) { JSDocInfo info = n.getJSDocInfo(); FunctionType functionType = (FunctionType) n.getJSType(); String functionPrivateName = n.getFirstChild().getString(); if (functionType.isInterface() || functionType.isConstructor()) { FunctionType baseConstructor = functionType. getPrototype().getImplicitPrototype().getConstructor(); if (baseConstructor != null && baseConstructor != getNativeType(OBJECT_FUNCTION_TYPE) && (baseConstructor.isConstructor() && functionType.isInterface() || baseConstructor.isInterface() && functionType.isConstructor())) { compiler.report( t.makeError(n, CONFLICTING_EXTENDED_TYPE, functionPrivateName)); } for (JSType baseInterface : functionType.getImplementedInterfaces()) { boolean badImplementedType = false; ObjectType baseInterfaceObj = ObjectType.cast(baseInterface); if (baseInterfaceObj != null) { FunctionType interfaceConstructor

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>), maxArgs != Integer.MAX_VALUE ? " and no more than " + maxArgs + " argument(s)" : ""); } } /** * Visits a RETURN node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. */ private void visitReturn(NodeTraversal t, Node n) { Node function = t.getEnclosingFunction(); // This is a misplaced return, but the real JS will fail to compile, // so let it go. if (function == null) { return; } JSType jsType = getJSType(function); if (jsType instanceof FunctionType) { FunctionType functionType = (FunctionType) jsType; JSType returnType = functionType.getReturnType(); // if no return type is specified, undefined must be returned // (it's a void function) if (returnType == null) { returnType = getNativeType(VOID_TYPE); } // fetching the returned value's type Node valueNode = n.getFirstChild(); JSType actualReturnType; if (valueNode == null) { actualReturnType = getNativeType(VOID_TYPE); valueNode = n; } else { actualReturnType = getJSType(valueNode); } // verifying validator.expectCanAssignTo(t, valueNode, actualReturnType, returnType, "inconsistent return type"); } } /** * This function unifies the type checking involved in the core binary * operators and the corresponding assignment operators. The representation * used internally is such that common code can handle both kinds of * operators easily. * * @param op The operator. * @param t The traversal object, needed to report errors. * @param n The node being checked. */ private void visitBinaryOperator(int op, NodeTraversal t, Node n) { Node left = n.getFirstChild(); JSType leftType = getJSType(left); Node right = n.getLastChild(); JSType rightType = getJSType(right); switch (op) { case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.LSH: case Token.RSH: case Token.ASSIGN_URSH: case Token.URSH: if (!leftType.matchesInt32Context()) { report(t, left, BIT_OPERATION, NodeUtil.opToStr(n.getType()), leftType.toString()); } if (!rightType.matchesUint32Context()) { report(t, right, BIT_OPERATION, NodeUtil.opToStr(n.getType()), rightType.toString()); } break; case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: case Token.ASSIGN_MUL: case Token.ASSIGN_SUB: case Token.DIV: case Token.MOD: case Token.MUL: case Token.SUB: validator.expectNumber(t, left, leftType, "left operand"); validator.expectNumber(t, right, rightType, "right operand"); break; case Token.ASSIGN_BITAND: case Token.ASSIGN_BITXOR: case Token

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>(Node n) { JSType jsType = n.getJSType(); if (jsType == null) { // TODO(nicksantos): This branch indicates a compiler bug, not worthy of // halting the compilation but we should log this and analyze to track // down why it happens. This is not critical and will be resolved over // time as the type checker is extended. return getNativeType(UNKNOWN_TYPE); } else { return jsType; } } /** * Gets the type of the node or {@code null} if the node's type is not a * function. */ private FunctionType getFunctionType(Node n) { JSType type = getJSType(n).restrictByNotNullOrUndefined(); if (type.isUnknownType()) { return typeRegistry.getNativeFunctionType(U2U_CONSTRUCTOR_TYPE); } else if (type instanceof FunctionType) { return (FunctionType) type; } else { return null; } } // TODO(nicksantos): TypeCheck should never be attaching types to nodes. // All types should be attached by TypeInference. This is not true today // for legacy reasons. There are a number of places where TypeInference // doesn't attach a type, as a signal to TypeCheck that it needs to check // that node's type. /** * Ensure that the given node has a type. If it does not have one, * attach the UNKNOWN_TYPE. */ private void ensureTyped(NodeTraversal t, Node n) { ensureTyped(t, n, getNativeType(UNKNOWN_TYPE)); } private void ensureTyped(NodeTraversal t, Node n, JSTypeNative type) { ensureTyped(t, n, getNativeType(type)); } /** * Enforces type casts, and ensures the node is typed. * * A cast in the way that we use it in JSDoc annotations never * alters the generated code and therefore never can induce any runtime * operation. What this means is that a 'cast' is really just a compile * time constraint on the underlying value. In the future, we may add * support for run-time casts for compiled tests. * * To ensure some shred of sanity, we enforce the notion that the * type you are casting to may only meaningfully be a narrower type * than the underlying declared type. We also invalidate optimizations * on bad type casts. * * @param t The traversal object needed to report errors. * @param n The node getting a type assigned to it. * @param type The type to be assigned. */ private void ensureTyped(NodeTraversal t, Node n, JSType type) { // Make sure FUNCTION nodes always get function type. Preconditions.checkState(n.getType() != Token.FUNCTION || type instanceof FunctionType || type.isUnknownType()); JSDocInfo info = n.getJSDocInfo(); if (info != null) { if (info.hasType()) { JSType infoType = info.getType().evaluate(t.getScope(), typeRegistry); validator.expectCanCast(t, n, infoType, type); type = infoType; } if (info.isImplicitCast() && !inExterns

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>/* * Copyright 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.Node; /** * Look for references to the global RegExp object that would cause * regular expressions to be unoptimizable. * * @author johnlenz@google.com (John Lenz) */ class CheckRegExp extends AbstractPostOrderCallback implements CompilerPass { static final DiagnosticType REGEXP_REFERENCE = DiagnosticType.warning("JSC_REGEXP_REFERENCE", "References to the global RegExp object prevents " + "optimization of regular expressions."); private final AbstractCompiler compiler; private boolean globalRegExpPropertiesUsed = false; public boolean isGlobalRegExpPropertiesUsed() { return globalRegExpPropertiesUsed; } public CheckRegExp(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, this); } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (NodeUtil.isReferenceName(n)) { String name = n.getString(); if (name.equals("RegExp") && t.getScope().getVar(name) == null) { int parentType = parent.getType(); boolean first = (n == parent.getFirstChild()); if (!((parentType == Token.NEW && first) || (parentType == Token.CALL && first) || (parentType == Token.INSTANCEOF && !first))) { t.report(n, REGEXP_REFERENCE); globalRegExpPropertiesUsed = true; } } } } }

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>.javascript.jscomp.mozilla.rhino.ast.NumberLiteral; import com.google.javascript.jscomp.mozilla.rhino.ast.ObjectLiteral; import com.google.javascript.jscomp.mozilla.rhino.ast.ObjectProperty; import com.google.javascript.jscomp.mozilla.rhino.ast.ParenthesizedExpression; import com.google.javascript.jscomp.mozilla.rhino.ast.PropertyGet; import com.google.javascript.jscomp.mozilla.rhino.ast.RegExpLiteral; import com.google.javascript.jscomp.mozilla.rhino.ast.ReturnStatement; import com.google.javascript.jscomp.mozilla.rhino.ast.Scope; import com.google.javascript.jscomp.mozilla.rhino.ast.StringLiteral; import com.google.javascript.jscomp.mozilla.rhino.ast.SwitchCase; import com.google.javascript.jscomp.mozilla.rhino.ast.SwitchStatement; import com.google.javascript.jscomp.mozilla.rhino.ast.ThrowStatement; import com.google.javascript.jscomp.mozilla.rhino.ast.TryStatement; import com.google.javascript.jscomp.mozilla.rhino.ast.UnaryExpression; import com.google.javascript.jscomp.mozilla.rhino.ast.VariableDeclaration; import com.google.javascript.jscomp.mozilla.rhino.ast.VariableInitializer; import com.google.javascript.jscomp.mozilla.rhino.ast.WhileLoop; import com.google.javascript.jscomp.mozilla.rhino.ast.WithStatement; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.util.Set; /** * IRFactory transforms the new AST to the old AST. * */ public class IRFactory { private final String sourceString; private final String sourceName; private final Config config; private final ErrorReporter errorReporter; private final TransformDispatcher transformDispatcher; // non-static for thread safety private final Set<String> ALLOWED_DIRECTIVES = Sets.newHashSet("use strict"); // @license text gets appended onto the fileLevelJsDocBuilder as found, // and stored in JSDocInfo for placeholder node. Node rootNodeJsDocHolder = new Node(Token.SCRIPT); Node.FileLevelJsDocBuilder fileLevelJsDocBuilder = rootNodeJsDocHolder.getJsDocBuilderForNode(); JSDocInfo fileOverviewInfo = null; // Use a template node for properties set on all nodes to minimize the // memory footprint associated with these. private Node templateNode; // TODO(johnlenz): Consider creating a template pool for ORIGINALNAME_PROP. private IRFactory(String sourceString, String sourceName, Config config, ErrorReporter errorReporter) { this.sourceString = sourceString; this.sourceName = sourceName; this.config = config; this.errorReporter = errorReporter; this.transformDispatcher = new TransformDispatcher(); // The template node properties are applied to all nodes in this transform. this.templateNode = createTemplateNode();

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> } // Create a template node to use as a source of common attributes, this allows // the prop structure to be shared among all the node from this source file. // This reduces the cost of these properties to O(nodes) to O(files). private Node createTemplateNode() { // The Node type choice is arbitrary. Node templateNode = new Node(Token.SCRIPT); templateNode.putProp(Node.SOURCENAME_PROP, sourceName); return templateNode; } public static Node transformTree(AstRoot node, String sourceString, Config config, ErrorReporter errorReporter) { IRFactory irFactory = new IRFactory(sourceString, node.getSourceName(), config, errorReporter); Node irNode = irFactory.transform(node); if (node.getComments() != null) { for (Comment comment : node.getComments()) { if (comment.getCommentType() == JSDOC && !comment.isParsed()) { irFactory.handlePossibleFileOverviewJsDoc(comment); } } } irFactory.setFileOverviewJsDoc(irNode); return irNode; } private void setFileOverviewJsDoc(Node irNode) { // Only after we've seen all @fileoverview entries, attach the // last one to the root node, and copy the found license strings // to that node. irNode.setJSDocInfo(rootNodeJsDocHolder.getJSDocInfo()); if (fileOverviewInfo != null) { if ((irNode.getJSDocInfo() != null) && (irNode.getJSDocInfo().getLicense() != null)) { fileOverviewInfo.setLicense(irNode.getJSDocInfo().getLicense()); } irNode.setJSDocInfo(fileOverviewInfo); } } private Node transformBlock(AstNode node) { Node irNode = transform(node); if (irNode.getType() != Token.BLOCK) { if (irNode.getType() == Token.EMPTY) { irNode.setType(Token.BLOCK); irNode.setWasEmptyNode(true); } else { Node newBlock = newNode(Token.BLOCK, irNode); newBlock.setLineno(irNode.getLineno()); newBlock.setCharno(irNode.getCharno()); irNode = newBlock; } } return irNode; } /** * @return true if the jsDocParser represents a fileoverview. */ private boolean handlePossibleFileOverviewJsDoc( JsDocInfoParser jsDocParser) { if (jsDocParser.getFileOverviewJSDocInfo() != fileOverviewInfo) { fileOverviewInfo = jsDocParser.getFileOverviewJSDocInfo(); return true; } return false; } private void handlePossibleFileOverviewJsDoc(Comment comment) { JsDocInfoParser jsDocParser = createJsDocInfoParser(comment); comment.setParsed(true); handlePossibleFileOverviewJsDoc(jsDocParser); } private JSDocInfo handleJsDoc(AstNode node) { Comment comment = node.getJsDocNode(); if (comment != null) { JsDocInfoParser jsDocParser = createJsDocInfoParser(comment); comment.setParsed

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>(true); if (!handlePossibleFileOverviewJsDoc(jsDocParser)) { return jsDocParser.retrieveAndResetParsedJSDocInfo(); } } return null; } private Node transform(AstNode node) { JSDocInfo jsDocInfo = handleJsDoc(node); Node irNode = justTransform(node); if (jsDocInfo != null) { irNode.setJSDocInfo(jsDocInfo); } // If we have a named function, set the position to that of the name. if (irNode.getType() == Token.FUNCTION && irNode.getFirstChild().getLineno() != -1) { irNode.setLineno(irNode.getFirstChild().getLineno()); irNode.setCharno(irNode.getFirstChild().getCharno()); } else { if (irNode.getLineno() == -1) { // If we didn't already set the line, then set it now. This avoids // cases like ParenthesizedExpression where we just return a previous // node, but don't want the new node to get its parent's line number. int lineno = node.getLineno(); irNode.setLineno(lineno); int charno = position2charno(node.getAbsolutePosition()); irNode.setCharno(charno); } } return irNode; } /** * Creates a JsDocInfoParser and parses the JsDoc string. * * Used both for handling individual JSDoc comments and for handling * file-level JSDoc comments (@fileoverview and @license). * * @param node The JsDoc Comment node to parse. * @return A JSDocInfoParser. Will contain either fileoverview jsdoc, or * normal jsdoc, or no jsdoc (if the method parses to the wrong level). */ private JsDocInfoParser createJsDocInfoParser(Comment node) { String comment = node.getValue(); int lineno = node.getLineno(); int position = node.getAbsolutePosition(); // The JsDocInfoParser expects the comment without the initial '/**'. int numOpeningChars = 3; JsDocInfoParser jsdocParser = new JsDocInfoParser( new JsDocTokenStream(comment.substring(numOpeningChars), lineno, position2charno(position) + numOpeningChars), sourceName, config, errorReporter); jsdocParser.setFileLevelJsDocBuilder(fileLevelJsDocBuilder); jsdocParser.setFileOverviewJSDocInfo(fileOverviewInfo); jsdocParser.parse(); return jsdocParser; } private int position2charno(int position) { int lineIndex = sourceString.lastIndexOf('\n', position); if (lineIndex == -1) { return position; } else { // Subtract one for initial position being 0. return position - lineIndex - 1; } } private Node justTransform(AstNode node) { return transformDispatcher.process(node); } private class TransformDispatcher extends TypeSafeDispatcher<Node> { private Node processGeneric( com.google.javascript.jscomp.mozilla.rhino.Node n) { Node node = newNode(transformTokenType(n.getType()));

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>NodeTraversal t, Map<Var, ReferenceCollection> referenceMap) { // Check all vars after finishing a scope for (Iterator<Var> it = t.getScope().getVars(); it.hasNext();) { Var v = it.next(); checkVar(t, v, referenceMap.get(v).references); } } /** * If the variable is declared more than once in a basic block, generate a * warning. Also check if a variable is used in a given scope before it is * declared, which suggest a likely error. Relies on the fact that * references is in parse-tree order. */ private void checkVar(NodeTraversal t, Var v, List<Reference> references) { blocksWithDeclarations.clear(); boolean isDeclaredInScope = false; boolean isUnhoistedNamedFunction = false; Reference hoistedFn = null; // Look for hoisted functions. for (Reference reference : references) { if (reference.isHoistedFunction()) { blocksWithDeclarations.add(reference.getBasicBlock()); isDeclaredInScope = true; hoistedFn = reference; break; } else if (NodeUtil.isFunctionDeclaration( reference.getNameNode().getParent())) { isUnhoistedNamedFunction = true; } } for (Reference reference : references) { if (reference == hoistedFn) { continue; } BasicBlock basicBlock = reference.getBasicBlock(); boolean isDeclaration = reference.isDeclaration(); if (isDeclaration) { // Look through all the declarations we've found so far, and // check if any of them are before this block. for (BasicBlock declaredBlock : blocksWithDeclarations) { if (declaredBlock.provablyExecutesBefore(basicBlock)) { compiler.report( JSError.make(reference.getSourceName(), reference.getNameNode(), checkLevel, REDECLARED_VARIABLE, v.name)); break; } } } if (isUnhoistedNamedFunction && !isDeclaration && isDeclaredInScope) { // Only allow an unhoisted named function to be used within the // block it is declared. for (BasicBlock declaredBlock : blocksWithDeclarations) { if (!declaredBlock.provablyExecutesBefore(basicBlock)) { compiler.report( JSError.make(reference.getSourceName(), reference.getNameNode(), AMBIGUOUS_FUNCTION_DECL, v.name)); break; } } } if (!isDeclaration && !isDeclaredInScope) { // Special case to deal with var goog = goog || {} Node grandparent = reference.getGrandparent(); if (grandparent.getType() == Token.NAME && grandparent.getString() == v.name) { continue; } // Only generate warnings if the scopes do not match in order // to deal with possible forward declarations and recursion if (reference.getScope() == v.scope) { compiler.report( JSError.make(reference.getSourceName(), reference.getNameNode(), checkLevel, UNDECLARED_REFERENCE, v.name)); } } if (isDeclaration) { blocksWithDeclarations.add(basicBlock); isDeclaredInScope = true; } } } } }

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>ImplicitPrototype(); if (implicitPrototype != null) { return implicitPrototype.isPropertyTypeInferred(property); } // property does not exist return false; } return p.inferred; } @Override public JSType getPropertyType(String propertyName) { Property p = properties.get(propertyName); if (p != null) { return p.type; } ObjectType implicitPrototype = getImplicitPrototype(); if (implicitPrototype != null) { return implicitPrototype.getPropertyType(propertyName); } return getNativeType(JSTypeNative.UNKNOWN_TYPE); } @Override public boolean isPropertyInExterns(String propertyName) { Property p = properties.get(propertyName); if (p != null) { return p.inExterns; } ObjectType implicitPrototype = getImplicitPrototype(); if (implicitPrototype != null) { return implicitPrototype.isPropertyInExterns(propertyName); } return false; } @Override boolean defineProperty(String name, JSType type, boolean inferred, boolean inExterns) { if (hasOwnDeclaredProperty(name)) { return false; } properties.put(name, new Property(type, inferred, inExterns)); return true; } @Override public JSDocInfo getOwnPropertyJSDocInfo(String propertyName) { Property p = properties.get(propertyName); if (p != null) { return p.docInfo; } return null; } @Override public void setPropertyJSDocInfo(String propertyName, JSDocInfo info, boolean inExterns) { if (info != null) { if (!properties.containsKey(propertyName)) { // If docInfo was attached, but the type of the property // was not defined anywhere, then we consider this an explicit // declaration of the property. defineInferredProperty(propertyName, getPropertyType(propertyName), inExterns); } // The prototype property is not represented as a normal Property. // We probably don't want to attach any JSDoc to it anyway. Property property = properties.get(propertyName); if (property != null) { property.docInfo = info; } } } @Override public boolean matchesNumberContext() { return isNumberObjectType() || isDateType() || isBooleanObjectType() || isStringObjectType() || hasOverridenNativeProperty("valueOf"); } @Override public boolean matchesStringContext() { return isTheObjectType() || isStringObjectType() || isDateType() || isRegexpType() || isArrayType() || isNumberObjectType() || isBooleanObjectType() || hasOverridenNativeProperty("toString"); } /** * Given the name of a native object property, checks whether the property is * present on the object and different from the native one. */ private boolean hasOverridenNativeProperty(String propertyName) { if (isNative()) { return false; } JSType propertyType = getPropertyType(propertyName); ObjectType nativeType = this.isFunctionType() ? registry.getNativeObjectType(JSTypeNative.FUNCTION_PROTOTYPE) : registry.getNativeObjectType(JSTypeNative.OBJECT_PROTOTYPE); JSType nativePropertyType = nativeType.getPropertyType(propertyName);

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> Interfaces // Find all the interfaces implemented by this class and compare each one // to the interface instance. ObjectType thatObj = that.toObjectType(); ObjectType thatCtor = thatObj == null ? null : thatObj.getConstructor(); if (thatCtor != null && thatCtor.isInterface()) { Iterable<ObjectType> thisInterfaces = getCtorImplementedInterfaces(); for (ObjectType thisInterface : thisInterfaces) { if (thisInterface.isSubtype(that)) { return true; } } } // other prototype based objects if (that != null) { if (isUnknownType() || implicitPrototypeChainIsUnknown()) { // If unsure, say 'yes', to avoid spurious warnings. // TODO(user): resolve the prototype chain completely in all cases, // to avoid guessing. return true; } return this.isImplicitPrototype(thatObj); } return false; } private boolean implicitPrototypeChainIsUnknown() { ObjectType p = getImplicitPrototype(); while (p != null) { if (p.isUnknownType()) { return true; } p = p.getImplicitPrototype(); } return false; } private static final class Property implements Serializable { private static final long serialVersionUID = 1L; /** * Property's type. */ private JSType type; /** * Whether the property's type is inferred. */ private final boolean inferred; /** * Whether the property is defined in the externs. */ private final boolean inExterns; /** The JSDocInfo for this property. */ private JSDocInfo docInfo = null; private Property(JSType type, boolean inferred, boolean inExterns) { this.type = type; this.inferred = inferred; this.inExterns = inExterns; } } @Override public boolean hasCachedValues() { return super.hasCachedValues(); } /** Whether this is a built-in object. */ @Override public boolean isNativeObjectType() { return nativeType; } @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) { setResolvedTypeInternal(this); // Don't try to resolve native types, because it's unnecessary and // there are infinite loops between native types. if (implicitPrototype != null && !implicitPrototype.isNativeObjectType()) { implicitPrototype = (ObjectType) implicitPrototype.resolve(t, scope); } for (Property prop : properties.values()) { prop.type = safeResolve(prop.type, t, scope); } return this; } }

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> } // Check if the sources need to be re-ordered. if (options.manageClosureDependencies) { for (CompilerInput input : inputs) { input.setCompiler(this); // Forward-declare all the provided types, so that they // are not flagged even if they are dropped from the process. for (String provide : input.getProvides()) { getTypeRegistry().forwardDeclareType(provide); } } try { inputs = (moduleGraph == null ? new JSModuleGraph(modules) : moduleGraph) .manageDependencies( options.manageClosureDependenciesEntryPoints, inputs); } catch (CircularDependencyException e) { report(JSError.make( JSModule.CIRCULAR_DEPENDENCY_ERROR, e.getMessage())); return null; } catch (MissingProvideException e) { report(JSError.make( MISSING_ENTRY_ERROR, e.getMessage())); return null; } } // Check if inputs need to be rebuilt from modules. boolean staleInputs = false; for (CompilerInput input : inputs) { Node n = input.getAstRoot(this); if (hasErrors()) { return null; } // Inputs can have a null AST during initial parse. if (n == null) { continue; } if (n.getJSDocInfo() != null) { JSDocInfo info = n.getJSDocInfo(); if (info.isExterns()) { // If the input file is explicitly marked as an externs file, then // assume the programmer made a mistake and throw it into // the externs pile anyways. externsRoot.addChildToBack(n); input.setIsExtern(true); input.getModule().remove(input); externs.add(input); staleInputs = true; } else if (info.isNoCompile()) { input.getModule().remove(input); staleInputs = true; } } } if (staleInputs) { fillEmptyModules(modules); rebuildInputsFromModules(); } // Build the AST. for (CompilerInput input : inputs) { Node n = input.getAstRoot(this); if (n == null) { continue; } if (devMode) { runSanityCheck(); if (hasErrors()) { return null; } } if (options.sourceMapOutputPath != null || options.nameReferenceReportPath != null) { // Annotate the nodes in the tree with information from the // input file. This information is used to construct the SourceMap. SourceInformationAnnotator sia = new SourceInformationAnnotator( input.getName(), options.devMode != DevMode.OFF); NodeTraversal.traverse(this, n, sia); } jsRoot.addChildToBack(n); } externAndJsRoot = new Node(Token.BLOCK, externsRoot, jsRoot); externAndJsRoot.setIsSyntheticBlock(true); return externAndJsRoot; } finally { stopTracer(tracer, "parseInputs"); } } public Node parse(JSSourceFile file) { initCompilerOptionsIfTesting(); addToDebugLog("Parsing: " + file.getName()); return new JsAst

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> inputs.get(i).getAstRoot(Compiler.this); if (scriptNode == null) { throw new IllegalArgumentException( "Bad module: " + module.getName()); } toSource(cb, i, scriptNode); } return cb.toString(); } }); } /** * Converts the parse tree for each input in a module back to js code. */ public String[] toSourceArray(final JSModule module) { return runInCompilerThread(new Callable<String[]>() { public String[] call() throws Exception { List<CompilerInput> inputs = module.getInputs(); int numInputs = inputs.size(); if (numInputs == 0) { return new String[0]; } String[] sources = new String[numInputs]; CodeBuilder cb = new CodeBuilder(); for (int i = 0; i < numInputs; i++) { Node scriptNode = inputs.get(i).getAstRoot(Compiler.this); if (scriptNode == null) { throw new IllegalArgumentException( "Bad module input: " + inputs.get(i).getName()); } cb.reset(); toSource(cb, i, scriptNode); sources[i] = cb.toString(); } return sources; } }); } /** * Writes out js code from a root node. If printing input delimiters, this * method will attach a comment to the start of the text indicating which * input the output derived from. If there were any preserve annotations * within the root's source, they will also be printed in a block comment * at the beginning of the output. */ public void toSource(final CodeBuilder cb, final int inputSeqNum, final Node root) { runInCompilerThread(new Callable<Void>() { public Void call() throws Exception { if (options.printInputDelimiter) { if ((cb.getLength() > 0) && !cb.endsWith("\n")) { cb.append("\n"); // Make sure that the label starts on a new line } Preconditions.checkState(root.getType() == Token.SCRIPT); String delimiter = options.inputDelimiter; String sourceName = (String)root.getProp(Node.SOURCENAME_PROP); Preconditions.checkState(sourceName != null); Preconditions.checkState(!sourceName.isEmpty()); delimiter = delimiter.replaceAll("%name%", sourceName) .replaceAll("%num%", String.valueOf(inputSeqNum)); cb.append(delimiter) .append("\n"); } if (root.getJSDocInfo() != null && root.getJSDocInfo().getLicense() != null) { cb.append("/*\n") .append(root.getJSDocInfo().getLicense()) .append("*/\n"); } // If there is a valid source map, then indicate to it that the current // root node's mappings are offset by the given string builder buffer. if (options.sourceMapOutputPath != null) { sourceMap.setStartingPosition( cb.getLineIndex(), cb.getColumnIndex()); } String code = toSource(root, sourceMap); if (!code.isEmpty()) { cb.append(code); if (!code.endsWith(";")) { cb.append("

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>;"); } } return null; } }); } /** * Generates JavaScript source code for an AST, doesn't generate source * map info. */ @Override String toSource(Node n) { initCompilerOptionsIfTesting(); return toSource(n, null); } /** * Generates JavaScript source code for an AST. */ private String toSource(Node n, SourceMap sourceMap) { CodePrinter.Builder builder = new CodePrinter.Builder(n); builder.setPrettyPrint(options.prettyPrint); builder.setLineBreak(options.lineBreak); builder.setSourceMap(sourceMap); builder.setSourceMapDetailLevel(options.sourceMapDetailLevel); Charset charset = options.outputCharset != null ? Charset.forName(options.outputCharset) : null; builder.setOutputCharset(charset); return builder.build(); } /** * Stores a buffer of text to which more can be appended. This is just like a * StringBuilder except that we also track the number of lines. */ public static class CodeBuilder { private final StringBuilder sb = new StringBuilder(); private int lineCount = 0; private int colCount = 0; /** Removes all text, but leaves the line count unchanged. */ void reset() { sb.setLength(0); } /** Appends the given string to the text buffer. */ CodeBuilder append(String str) { sb.append(str); // Adjust the line and column information for the new text. int index = -1; int lastIndex = index; while ((index = str.indexOf('\n', index + 1)) >= 0) { ++lineCount; lastIndex = index; } if (lastIndex == -1) { // No new lines, append the new characters added. colCount += str.length(); } else { colCount = str.length() - (lastIndex + 1); } return this; } /** Returns all text in the text buffer. */ @Override public String toString() { return sb.toString(); } /** Returns the length of the text buffer. */ public int getLength() { return sb.length(); } /** Returns the (zero-based) index of the last line in the text buffer. */ int getLineIndex() { return lineCount; } /** Returns the (zero-based) index of the last column in the text buffer. */ int getColumnIndex() { return colCount; } /** Determines whether the text ends with the given suffix. */ boolean endsWith(String suffix) { return (sb.length() > suffix.length()) && suffix.equals(sb.substring(sb.length() - suffix.length())); } } //------------------------------------------------------------------------ // Optimizations //------------------------------------------------------------------------ public void optimize() { // Ideally, this pass should be the first pass run, however: // 1) VariableReferenceCheck reports unexpected warnings if Normalize // is done first. // 2) ReplaceMessages, stripCode, and potentially custom passes rely on // unmodified local names. normalize(); PhaseOptimizer phaseOptimizer = new PhaseOptimizer(this, tracker); if (options.devMode == DevMode.EVERY_PASS) { phase

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> * @since 1.6R6 */ public String getScriptStackTrace() { return getScriptStackTrace(new FilenameFilter() { public boolean accept(File dir, String name) { return name.endsWith(".js"); } }); } /** * Get a string representing the script stack of this exception. * If optimization is enabled, this corresponds to all java stack elements * with a source name matching the <code>filter</code>. * @param filter the file name filter to determine whether a file is a * script file * @return a script stack dump * @since 1.6R6 */ public String getScriptStackTrace(FilenameFilter filter) { // The real Rhino code here has been removed. return "<No stack trace available>"; } @Override public void printStackTrace(PrintWriter s) { if (interpreterStackInfo == null) { super.printStackTrace(s); } else { s.print(generateStackTrace()); } } @Override public void printStackTrace(PrintStream s) { if (interpreterStackInfo == null) { super.printStackTrace(s); } else { s.print(generateStackTrace()); } } private String sourceName; private int lineNumber; private String lineSource; private int columnNumber; Object interpreterStackInfo; int[] interpreterLineData; }

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> are * declared or inferred. * */ public abstract class ObjectType extends JSType { private boolean visited; private JSDocInfo docInfo = null; private boolean unknown = true; ObjectType(JSTypeRegistry registry) { super(registry); } /** * Gets the declared default element type. * @see ParameterizedType */ public JSType getParameterType() { return null; } /** * Gets the declared default index type. * @see IndexedType */ public JSType getIndexType() { return null; } /** * Gets the docInfo for this type. */ @Override public JSDocInfo getJSDocInfo() { if (docInfo != null) { return docInfo; } else if (getImplicitPrototype() != null) { return getImplicitPrototype().getJSDocInfo(); } else { return super.getJSDocInfo(); } } /** * Sets the docInfo for this type from the given * {@link JSDocInfo}. The {@code JSDocInfo} may be {@code null}. */ public void setJSDocInfo(JSDocInfo info) { docInfo = info; } /** * Detects a cycle in the implicit prototype chain. This method accesses * the {@link #getImplicitPrototype()} method and must therefore be * invoked only after the object is sufficiently initialized to respond to * calls to this method.<p> * * The method is not thread safe.<p> * * @return True iff an implicit prototype cycle was detected. */ final boolean detectImplicitPrototypeCycle() { // detecting cycle this.visited = true; ObjectType p = getImplicitPrototype(); while (p != null) { if (p.visited) { return true; } else { p.visited = true; } p = p.getImplicitPrototype(); } // clean up p = this; do { p.visited = false; p = p.getImplicitPrototype(); } while (p != null); return false; } /** * Gets the reference name for this object. This includes named types * like constructors, prototypes, and enums. It notably does not include * literal types like strings and booleans and structural types. * @return the object's name or {@code null} if this is an anonymous * object */ public abstract String getReferenceName(); /** * Due to the complexity of some of our internal type systems, sometimes * we have different types constructed by the same constructor. * In other parts of the type system, these are called delegates. * We construct these types by appending suffixes to the constructor name. * * The normalized reference name does not have these suffixes, and as such, * recollapses these implicit types back to their real type. */ public String getNormalizedReferenceName() { String name = getReferenceName(); if (name != null) { int pos = name.indexOf("("); if (pos != -1) { return name.substring(0, pos); } } return name; } /** * Creates a suffix for a proxy delegate. * @see #getNormalizedReferenceName */ public static String

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> * property could be called, so setting this incorrectly could result * in live code being removed. * @return True if the property was registered successfully, false if this * conflicts with a previous property type declaration. */ abstract boolean defineProperty(String propertyName, JSType type, boolean inferred, boolean inExterns); /** * Gets the docInfo on the specified property on this type. This should not * be done implemented recursively, as you generally need to know exactly on * which type in the prototype chain the JSDocInfo exists. */ public JSDocInfo getOwnPropertyJSDocInfo(String propertyName) { return null; } /** * Sets the docInfo for the specified property from the * {@link JSDocInfo} on its definition. * @param info {@code JSDocInfo} for the property definition. May be * {@code null}. * @param inExterns {@code true} if this property was defined in an externs * file. TightenTypes assumes that any function passed to an externs * property could be called, so setting this incorrectly could result * in live code being removed. */ public void setPropertyJSDocInfo(String propertyName, JSDocInfo info, boolean inExterns) { // by default, do nothing } @Override public JSType findPropertyType(String propertyName) { return hasProperty(propertyName) ? getPropertyType(propertyName) : null; } /** * Gets the property type of the property whose name is given. If the * underlying object does not have this property, the Unknown type is * returned to indicate that no information is available on this property. * * @return the property's type or {@link UnknownType}. This method never * returns {@code null}. */ public abstract JSType getPropertyType(String propertyName); /** * Checks whether the property whose name is given is present on the * object. */ public abstract boolean hasProperty(String propertyName); /** * Checks whether the property whose name is given is present directly on * the object. Returns false even if it is declared on a supertype. */ public boolean hasOwnProperty(String propertyName) { return hasProperty(propertyName); } /** Returns the names of all the properties directly on this type. */ public Set<String> getOwnPropertyNames() { return ImmutableSet.of(); } /** * Checks whether the property's type is inferred. */ public abstract boolean isPropertyTypeInferred(String propertyName); /** * Checks whether the property's type is declared. */ public abstract boolean isPropertyTypeDeclared(String propertyName); /** * Whether the given property is declared on this object. */ boolean hasOwnDeclaredProperty(String name) { return hasOwnProperty(name) && isPropertyTypeDeclared(name); } /** Checks whether the property was defined in the externs. */ public boolean isPropertyInExterns(String propertyName) { return false; } /** * Gets the number of properties of this object. */ public abstract int getPropertiesCount(); /** * Returns a list of properties defined or inferred on this type and any of * its supertypes. */ public Set<String> getPropertyNames() { Set<String> props = Sets.new

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> if (!fnName.isEmpty() && NodeUtil.isFunctionExpression(n)) { declareVar(fnName, fnNameNode, n, null, null, n); } // Args: Declare function variables Preconditions.checkState(args.getType() == Token.LP); for (Node a = args.getFirstChild(); a != null; a = a.getNext()) { Preconditions.checkState(a.getType() == Token.NAME); declareVar(a.getString(), a, args, n, null, n); } // Body scanVars(body, n); } else { // It's the global block Preconditions.checkState(scope.getParent() == null); scanVars(n, null); } } /** * Scans and gather variables declarations under a Node */ private void scanVars(Node n, Node parent) { switch (n.getType()) { case Token.VAR: // Declare all variables. e.g. var x = 1, y, z; for (Node child = n.getFirstChild(); child != null;) { Node next = child.getNext(); Preconditions.checkState(child.getType() == Token.NAME); String name = child.getString(); declareVar(name, child, n, parent, null, n); child = next; } return; case Token.FUNCTION: if (NodeUtil.isFunctionExpression(n)) { return; } String fnName = n.getFirstChild().getString(); if (fnName.isEmpty()) { // This is invalid, but allow it so the checks can catch it. return; } declareVar(fnName, n.getFirstChild(), n, parent, null, n); return; // should not examine function's children case Token.CATCH: Preconditions.checkState(n.getChildCount() == 2); Preconditions.checkState(n.getFirstChild().getType() == Token.NAME); // the first child is the catch var and the third child // is the code block final Node var = n.getFirstChild(); final Node block = var.getNext(); declareVar(var.getString(), var, n, parent, null, n); scanVars(block, n); return; // only one child to scan case Token.SCRIPT: sourceName = (String) n.getProp(Node.SOURCENAME_PROP); break; } // Variables can only occur in statement-level nodes, so // we only need to traverse children in a couple special cases. if (NodeUtil.isControlStructure(n) || NodeUtil.isStatementBlock(n)) { for (Node child = n.getFirstChild(); child != null;) { Node next = child.getNext(); scanVars(child, n); child = next; } } } /** * Interface for injectable duplicate handling. */ interface RedeclarationHandler { void onRedeclaration( Scope s, String name, Node n, Node parent, Node gramps, Node nodeWithLineNumber); } /** * The default handler for duplicate declarations. */ private class DefaultRedeclarationHandler implements RedeclarationHandler { public void onRedeclaration( Scope s, String name, Node n, Node parent, Node gramps, Node nodeWithLineNumber

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>) { // Don't allow multiple variables to be declared at the top level scope if (scope.isGlobal()) { Scope.Var origVar = scope.getVar(name); Node origParent = origVar.getParentNode(); if (origParent.getType() == Token.CATCH && parent.getType() == Token.CATCH) { // Okay, both are 'catch(x)' variables. return; } boolean allowDupe = false; JSDocInfo info = n.getJSDocInfo(); if (info == null) { info = parent.getJSDocInfo(); } allowDupe = info != null && info.getSuppressions().contains("duplicate"); if (!allowDupe) { compiler.report( JSError.make(sourceName, nodeWithLineNumber, VAR_MULTIPLY_DECLARED_ERROR, name, (origVar.input != null ? origVar.input.getName() : "??"))); } } else if (name.equals(ARGUMENTS) && !NodeUtil.isVarDeclaration(n)) { // Disallow shadowing "arguments" as we can't handle with our current // scope modeling. compiler.report( JSError.make(sourceName, nodeWithLineNumber, VAR_ARGUMENTS_SHADOWED_ERROR)); } } } /** * Declares a variable. * * @param name The variable name * @param n The node corresponding to the variable name (usually a NAME node) * @param parent The parent node of {@code n} * @param gramps The parent node of {@code parent} * @param declaredType The variable's type, according to JSDoc * @param nodeWithLineNumber The node to use to access the line number of * the variable declaration, if needed */ private void declareVar(String name, Node n, Node parent, Node gramps, JSType declaredType, Node nodeWithLineNumber) { if (scope.isDeclared(name, false) || (scope.isLocal() && name.equals(ARGUMENTS))) { redeclarationHandler.onRedeclaration( scope, name, n, parent, gramps, nodeWithLineNumber); } else { scope.declare(name, n, declaredType, compiler.getInput(sourceName)); } } }

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>/* * Copyright 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.collect.Sets; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.jstype.FunctionType; import com.google.javascript.rhino.jstype.JSType; import com.google.javascript.rhino.jstype.ObjectType; import java.nio.charset.Charset; import java.util.Set; /** * A code generator that outputs type annotations for functions and * constructors. */ class TypedCodeGenerator extends CodeGenerator { TypedCodeGenerator(CodeConsumer consumer, Charset outputCharset) { super(consumer, outputCharset); } @Override void add(Node n, Context context) { Node parent = n.getParent(); if (parent != null && (parent.getType() == Token.BLOCK || parent.getType() == Token.SCRIPT)) { if (n.getType() == Token.FUNCTION) { add(getFunctionAnnotation(n)); } else if (n.getType() == Token.EXPR_RESULT && n.getFirstChild().getType() == Token.ASSIGN) { Node rhs = n.getFirstChild().getLastChild(); add(getTypeAnnotation(rhs)); } else if (n.getType() == Token.VAR && n.getFirstChild().getFirstChild() != null && n.getFirstChild().getFirstChild().getType() == Token.FUNCTION) { add(getFunctionAnnotation(n.getFirstChild().getFirstChild())); } } super.add(n, context); } private String getTypeAnnotation(Node node) { JSType type = node.getJSType(); if (type instanceof FunctionType) { return getFunctionAnnotation(node); } else if (type != null && !type.isUnknownType() && !type.isEmptyType() && !type.isVoidType() && !type.isFunctionPrototypeType()) { return "/** @type {" + node.getJSType() + "} */\n"; } else { return ""; } } /** * @param fnNode A node for a function for which to generate a type annotation */ private String getFunctionAnnotation(Node fnNode) { Preconditions.checkState(fnNode.getType() == Token.FUNCTION); StringBuilder sb = new StringBuilder("/**\n"); JSType type = fnNode.getJSType(); if (type == null || type.isUnknownType()) { return ""; } FunctionType funType = (FunctionType) fnNode.getJSType();

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> // We need to use the child nodes of the function as the nodes for the // parameters of the function type do not have the real parameter names. // FUNCTION // NAME // LP // NAME param1 // NAME param2 if (fnNode != null) { Node paramNode = NodeUtil.getFnParameters(fnNode).getFirstChild(); // Param types for (Node n : funType.getParameters()) { // Bail out if the paramNode is not there. if (paramNode == null) { break; } sb.append(" * @param {" + getParameterNodeJSDocType(n) + "} "); sb.append(paramNode.getString()); sb.append("\n"); paramNode = paramNode.getNext(); } } // Return type JSType retType = funType.getReturnType(); if (retType != null && !retType.isUnknownType() && !retType.isEmptyType()) { sb.append(" * @return {" + retType + "}\n"); } // Constructor/interface if (funType.isConstructor() || funType.isInterface()) { FunctionType superConstructor = funType.getSuperClassConstructor(); if (superConstructor != null) { ObjectType superInstance = funType.getSuperClassConstructor().getInstanceType(); if (!superInstance.toString().equals("Object")) { sb.append(" * @extends {" + superInstance + "}\n"); } } // Avoid duplicates, add implemented type to a set first Set<String> interfaces = Sets.newTreeSet(); for (ObjectType interfaze : funType.getImplementedInterfaces()) { interfaces.add(interfaze.toString()); } for (String interfaze : interfaces) { sb.append(" * @implements {" + interfaze + "}\n"); } if (funType.isConstructor()) { sb.append(" * @constructor\n"); } else if (funType.isInterface()) { sb.append(" * @interface\n"); } } if (fnNode != null && fnNode.getBooleanProp(Node.IS_DISPATCHER)) { sb.append(" * @javadispatch\n"); } sb.append(" */\n"); return sb.toString(); } /** * Creates a JSDoc-suitable String representation the type of a parameter. * * @param parameterNode The parameter node. */ private String getParameterNodeJSDocType(Node parameterNode) { JSType parameterType = parameterNode.getJSType(); String typeString; // Emit unknown types as '*' (AllType) since '?' (UnknownType) is not // a valid JSDoc type. if (parameterType.isUnknownType()) { typeString = "*"; } else { // Fix-up optional and vararg parameters to match JSDoc type language if (parameterNode.isOptionalArg()) { typeString = parameterType.restrictByNotNullOrUndefined() + "="; } else if (parameterNode.isVarArgs()) { typeString = "..." + parameterType.restrictByNotNullOrUndefined(); } else { typeString = parameterType.toString(); } } return typeString; } }

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> null) { referencedObjType.collectPropertyNames(props); } } @Override public JSType findPropertyType(String propertyName) { return referencedType.findPropertyType(propertyName); } @Override public JSType getPropertyType(String propertyName) { return referencedObjType == null ? getNativeType(JSTypeNative.UNKNOWN_TYPE) : referencedObjType.getPropertyType(propertyName); } @Override public JSDocInfo getJSDocInfo() { return referencedType.getJSDocInfo(); } @Override public void setJSDocInfo(JSDocInfo info) { if (referencedObjType != null) { referencedObjType.setJSDocInfo(info); } } @Override public JSDocInfo getOwnPropertyJSDocInfo(String propertyName) { return referencedObjType == null ? null : referencedObjType.getOwnPropertyJSDocInfo(propertyName); } @Override public void setPropertyJSDocInfo(String propertyName, JSDocInfo info, boolean inExterns) { if (referencedObjType != null) { referencedObjType.setPropertyJSDocInfo(propertyName, info, inExterns); } } @Override public boolean hasProperty(String propertyName) { return referencedObjType == null ? false : referencedObjType.hasProperty(propertyName); } @Override public boolean hasOwnProperty(String propertyName) { return referencedObjType == null ? false : referencedObjType.hasOwnProperty(propertyName); } @Override public Set<String> getOwnPropertyNames() { return referencedObjType == null ? ImmutableSet.<String>of() : referencedObjType.getOwnPropertyNames(); } @Override public FunctionType getConstructor() { return referencedObjType == null ? null : referencedObjType.getConstructor(); } @Override public JSType getParameterType() { return referencedObjType == null ? null : referencedObjType.getParameterType(); } @Override public JSType getIndexType() { return referencedObjType == null ? null : referencedObjType.getIndexType(); } @Override public <T> T visit(Visitor<T> visitor) { return referencedType.visit(visitor); } @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) { setReferencedType(referencedType.resolve(t, scope)); return this; } @Override public String toDebugHashCodeString() { return "{proxy:" + referencedType.toDebugHashCodeString() + "}"; } }

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> been deprecated: {1}"); static final DiagnosticType BAD_PRIVATE_GLOBAL_ACCESS = DiagnosticType.disabled( "JSC_BAD_PRIVATE_GLOBAL_ACCESS", "Access to private variable {0} not allowed outside file {1}."); static final DiagnosticType BAD_PRIVATE_PROPERTY_ACCESS = DiagnosticType.disabled( "JSC_BAD_PRIVATE_PROPERTY_ACCESS", "Access to private property {0} of {1} not allowed here."); static final DiagnosticType BAD_PROTECTED_PROPERTY_ACCESS = DiagnosticType.disabled( "JSC_BAD_PROTECTED_PROPERTY_ACCESS", "Access to protected property {0} of {1} not allowed here."); static final DiagnosticType PRIVATE_OVERRIDE = DiagnosticType.disabled( "JSC_PRIVATE_OVERRIDE", "Overriding private property of {0}."); static final DiagnosticType VISIBILITY_MISMATCH = DiagnosticType.disabled( "JSC_VISIBILITY_MISMATCH", "Overriding {0} property of {1} with {2} property."); private final AbstractCompiler compiler; private final TypeValidator validator; // State about the current traversal. private int deprecatedDepth = 0; private int methodDepth = 0; private JSType currentClass = null; CheckAccessControls(AbstractCompiler compiler) { this.compiler = compiler; this.validator = compiler.getTypeValidator(); } public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, this); } public void enterScope(NodeTraversal t) { if (!t.inGlobalScope()) { Node n = t.getScopeRoot(); Node parent = n.getParent(); if (isDeprecatedFunction(n, parent)) { deprecatedDepth++; } if (methodDepth == 0) { currentClass = getClassOfMethod(n, parent); } methodDepth++; } } public void exitScope(NodeTraversal t) { if (!t.inGlobalScope()) { Node n = t.getScopeRoot(); Node parent = n.getParent(); if (isDeprecatedFunction(n, parent)) { deprecatedDepth--; } methodDepth--; if (methodDepth == 0) { currentClass = null; } } } /** * Gets the type of the class that "owns" a method, or null if * we know that its un-owned. */ private JSType getClassOfMethod(Node n, Node parent) { if (parent.getType() == Token.ASSIGN) { Node lValue = parent.getFirstChild(); if (lValue.isQualifiedName()) { if (lValue.getType() == Token.GETPROP) { // We have an assignment of the form "a.b = ...". JSType lValueType = lValue.getJSType(); if (lValueType != null && lValueType.isConstructor()) { // If a.b is a constructor, then everything in this function // belongs to the "a.b" type. return ((FunctionType) lValueType).getInstanceType(); } else { // If a.b is not a constructor, then treat this as a method // of whatever type is on "a". return normalizeClassType(lValue.getFirstChild().getJSType()); } } else

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> { // We have an assignment of the form "a = ...", so pull the // type off the "a". return normalizeClassType(lValue.getJSType()); } } } else if (NodeUtil.isFunctionDeclaration(n) || parent.getType() == Token.NAME) { return normalizeClassType(n.getJSType()); } return null; } /** * Normalize the type of a constructor, its instance, and its prototype * all down to the same type (the instance type). */ private JSType normalizeClassType(JSType type) { if (type == null || type.isUnknownType()) { return type; } else if (type.isConstructor()) { return ((FunctionType) type).getInstanceType(); } else if (type.isFunctionPrototypeType()) { FunctionType owner = ((FunctionPrototypeType) type).getOwnerFunction(); if (owner.isConstructor()) { return owner.getInstanceType(); } } return type; } public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { return true; } public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.NAME: checkNameDeprecation(t, n, parent); checkNameVisibility(t, n, parent); break; case Token.GETPROP: checkPropertyDeprecation(t, n, parent); checkPropertyVisibility(t, n, parent); break; case Token.NEW: checkConstructorDeprecation(t, n, parent); break; } } /** * Checks the given NEW node to ensure that access restrictions are obeyed. */ private void checkConstructorDeprecation(NodeTraversal t, Node n, Node parent) { JSType type = n.getJSType(); if (type != null) { String deprecationInfo = getTypeDeprecationInfo(type); if (deprecationInfo != null && shouldEmitDeprecationWarning(t, n, parent)) { if (!deprecationInfo.isEmpty()) { compiler.report( t.makeError(n, DEPRECATED_CLASS_REASON, type.toString(), deprecationInfo)); } else { compiler.report( t.makeError(n, DEPRECATED_CLASS, type.toString())); } } } } /** * Checks the given NAME node to ensure that access restrictions are obeyed. */ private void checkNameDeprecation(NodeTraversal t, Node n, Node parent) { // Don't bother checking definitions or constructors. if (parent.getType() == Token.FUNCTION || parent.getType() == Token.VAR || parent.getType() == Token.NEW) { return; } Scope.Var var = t.getScope().getVar(n.getString()); JSDocInfo docInfo = var == null ? null : var.getJSDocInfo(); if (docInfo != null && docInfo.isDeprecated() && shouldEmitDeprecationWarning(t, n, parent)) { if (docInfo.getDeprecationReason() != null) { compiler.report( t.makeError(n, DEPRECATED_NAME_REASON, n.getString(), docInfo.getDeprecationReason())); } else { compiler.report( t.makeError

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>(n, DEPRECATED_NAME, n.getString())); } } } /** * Checks the given GETPROP node to ensure that access restrictions are * obeyed. */ private void checkPropertyDeprecation(NodeTraversal t, Node n, Node parent) { // Don't bother checking constructors. if (parent.getType() == Token.NEW) { return; } ObjectType objectType = ObjectType.cast(dereference(n.getFirstChild().getJSType())); String propertyName = n.getLastChild().getString(); if (objectType != null) { String deprecationInfo = getPropertyDeprecationInfo(objectType, propertyName); if (deprecationInfo != null && shouldEmitDeprecationWarning(t, n, parent)) { if (!deprecationInfo.isEmpty()) { compiler.report( t.makeError(n, DEPRECATED_PROP_REASON, propertyName, validator.getReadableJSTypeName(n.getFirstChild(), true), deprecationInfo)); } else { compiler.report( t.makeError(n, DEPRECATED_PROP, propertyName, validator.getReadableJSTypeName(n.getFirstChild(), true))); } } } } /** * Determines whether the given name is visible in the current context. * @param t The current traversal. * @param name The name node. */ private void checkNameVisibility(NodeTraversal t, Node name, Node parent) { Var var = t.getScope().getVar(name.getString()); if (var != null) { JSDocInfo docInfo = var.getJSDocInfo(); if (docInfo != null) { // If a name is private, make sure that we're in the same file. Visibility visibility = docInfo.getVisibility(); if (visibility == Visibility.PRIVATE && !t.getInput().getName().equals(docInfo.getSourceName())) { if (docInfo.isConstructor() && isValidPrivateConstructorAccess(parent)) { return; } compiler.report( t.makeError(name, BAD_PRIVATE_GLOBAL_ACCESS, name.getString(), docInfo.getSourceName())); } } } } /** * Determines whether the given property is visible in the current context. * @param t The current traversal. * @param getprop The getprop node. */ private void checkPropertyVisibility(NodeTraversal t, Node getprop, Node parent) { ObjectType objectType = ObjectType.cast(dereference(getprop.getFirstChild().getJSType())); String propertyName = getprop.getLastChild().getString(); if (objectType != null) { // Is this a normal property access, or are we trying to override // an existing property? boolean isOverride = t.inGlobalScope() && parent.getType() == Token.ASSIGN && parent.getFirstChild() == getprop; // Find the lowest property defined on a class with visibility // information. if (isOverride) { objectType = objectType.getImplicitPrototype(); } JSDocInfo docInfo = null; for (; objectType != null; objectType = objectType.getImplicitPrototype()) { docInfo = objectType.getOwnPropertyJSDocInfo(propertyName); if (docInfo != null && docInfo.getVisibility() != Visibility.INHERITED

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>) { break; } } if (objectType == null) { // We couldn't find a visibility modifier; assume it's public. return; } boolean sameInput = t.getInput().getName().equals(docInfo.getSourceName()); Visibility visibility = docInfo.getVisibility(); JSType ownerType = normalizeClassType(objectType); if (isOverride) { // Check an ASSIGN statement that's trying to override a property // on a superclass. JSDocInfo overridingInfo = parent.getJSDocInfo(); Visibility overridingVisibility = overridingInfo == null ? Visibility.INHERITED : overridingInfo.getVisibility(); // Check that (a) the property *can* be overridden, and // (b) that the visibility of the override is the same as the // visibility of the original property. if (visibility == Visibility.PRIVATE && !sameInput) { compiler.report( t.makeError(getprop, PRIVATE_OVERRIDE, objectType.toString())); } else if (overridingVisibility != Visibility.INHERITED && overridingVisibility != visibility) { compiler.report( t.makeError(getprop, VISIBILITY_MISMATCH, visibility.name(), objectType.toString(), overridingVisibility.name())); } } else { if (sameInput) { // private access is always allowed in the same file. return; } else if (visibility == Visibility.PRIVATE && (currentClass == null || ownerType.differsFrom(currentClass))) { if (docInfo.isConstructor() && isValidPrivateConstructorAccess(parent)) { return; } // private access is not allowed outside the file from a different // enclosing class. compiler.report( t.makeError(getprop, BAD_PRIVATE_PROPERTY_ACCESS, propertyName, validator.getReadableJSTypeName( getprop.getFirstChild(), true))); } else if (visibility == Visibility.PROTECTED) { // There are 3 types of legal accesses of a protected property: // 1) Accesses in the same file // 2) Overriding the property in a subclass // 3) Accessing the property from inside a subclass // The first two have already been checked for. if (currentClass == null || !currentClass.isSubtype(ownerType)) { compiler.report( t.makeError(getprop, BAD_PROTECTED_PROPERTY_ACCESS, propertyName, validator.getReadableJSTypeName( getprop.getFirstChild(), true))); } } } } } /** * Whether the given access of a private constructor is legal. * * For example, * new PrivateCtor_(); // not legal * PrivateCtor_.newInstance(); // legal * x instanceof PrivateCtor_ // legal * * This is a weird special case, because our visibility system is inherited * from Java, and JavaScript has no distinction between classes and * constructors like Java does. * * We may want to revisit this if we decide to make the restrictions tighter. */ private static boolean isValidPrivateConstructorAccess(Node parent) { return parent.getType() != Token.NEW; } /** * Determines whether a deprecation warning should be emitted. * @param t The current traversal. * @param n The node which

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> we are checking. * @param parent The parent of the node which we are checking. */ private boolean shouldEmitDeprecationWarning( NodeTraversal t, Node n, Node parent) { // In the global scope, there are only two kinds of accesses that should // be flagged for warnings: // 1) Calls of deprecated functions and methods. // 2) Instantiations of deprecated classes. // For now, we just let everything else by. if (t.inGlobalScope()) { if (!((parent.getType() == Token.CALL && parent.getFirstChild() == n) || n.getType() == Token.NEW)) { return false; } } // We can always assign to a deprecated property, to keep it up to date. if (n.getType() == Token.GETPROP && n == parent.getFirstChild() && NodeUtil.isAssignmentOp(parent)) { return false; } return !canAccessDeprecatedTypes(t); } /** * Returns whether it's currently ok to access deprecated names and * properties. * * There are 3 exceptions when we're allowed to use a deprecated * type or property: * 1) When we're in a deprecated function. * 2) When we're in a deprecated class. * 3) When we're in a static method of a deprecated class. */ private boolean canAccessDeprecatedTypes(NodeTraversal t) { Node scopeRoot = t.getScopeRoot(); Node scopeRootParent = scopeRoot.getParent(); return // Case #1 (deprecatedDepth > 0) || // Case #2 (getTypeDeprecationInfo(t.getScope().getTypeOfThis()) != null) || // Case #3 (scopeRootParent != null && scopeRootParent.getType() == Token.ASSIGN && getTypeDeprecationInfo( getClassOfMethod(scopeRoot, scopeRootParent)) != null); } /** * Returns whether this is a function node annotated as deprecated. */ private static boolean isDeprecatedFunction(Node n, Node parent) { if (n.getType() == Token.FUNCTION) { JSType type = n.getJSType(); if (type != null) { return getTypeDeprecationInfo(type) != null; } } return false; } /** * Returns the deprecation reason for the type if it is marked * as being deprecated. Returns empty string if the type is deprecated * but no reason was given. Returns null if the type is not deprecated. */ private static String getTypeDeprecationInfo(JSType type) { if (type == null) { return null; } JSDocInfo info = type.getJSDocInfo(); if (info != null && info.isDeprecated()) { if (info.getDeprecationReason() != null) { return info.getDeprecationReason(); } return ""; } ObjectType objType = ObjectType.cast(type); if (objType != null) { ObjectType implicitProto = objType.getImplicitPrototype(); if (implicitProto != null) { return getTypeDeprecationInfo(implicitProto); } } return null; } /** * Returns the deprecation reason for the property if it is marked * as being deprecated. Returns empty string if the property is deprecated *

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> but no reason was given. Returns null if the property is not deprecated. */ private static String getPropertyDeprecationInfo(ObjectType type, String prop) { JSDocInfo info = type.getOwnPropertyJSDocInfo(prop); if (info != null && info.isDeprecated()) { if (info.getDeprecationReason() != null) { return info.getDeprecationReason(); } return ""; } ObjectType implicitProto = type.getImplicitPrototype(); if (implicitProto != null) { return getPropertyDeprecationInfo(implicitProto, prop); } return null; } /** * Dereference a type, autoboxing it and filtering out null. */ private static JSType dereference(JSType type) { return type == null ? null : type.dereference(); } }

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>externs, root); } }; } }; /** Checks type usage */ private final PassFactory checkTypes = new PassFactory("checkTypes", false) { @Override protected CompilerPass createInternal(final AbstractCompiler compiler) { return new CompilerPass() { @Override public void process(Node externs, Node root) { Preconditions.checkNotNull(topScope); Preconditions.checkNotNull(typedScopeCreator); TypeCheck check = makeTypeCheck(compiler); check.process(externs, root); compiler.getErrorManager().setTypedPercent(check.getTypedPercent()); } }; } }; /** * Checks possible execution paths of the program for problems: missing return * statements and dead code. */ private final PassFactory checkControlFlow = new PassFactory("checkControlFlow", true) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { List<Callback> callbacks = Lists.newArrayList(); if (options.checkUnreachableCode.isOn()) { callbacks.add( new CheckUnreachableCode(compiler, options.checkUnreachableCode)); } if (options.checkMissingReturn.isOn() && options.checkTypes) { callbacks.add( new CheckMissingReturn(compiler, options.checkMissingReturn)); } return combineChecks(compiler, callbacks); } }; /** Checks access controls. Depends on type-inference. */ private final PassFactory checkAccessControls = new PassFactory("checkAccessControls", true) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { return new CheckAccessControls(compiler); } }; /** Executes the given callbacks with a {@link CombinedCompilerPass}. */ private static CompilerPass combineChecks(AbstractCompiler compiler, List<Callback> callbacks) { Preconditions.checkArgument(callbacks.size() > 0); Callback[] array = callbacks.toArray(new Callback[callbacks.size()]); return new CombinedCompilerPass(compiler, array); } /** A compiler pass that resolves types in the global scope. */ private class GlobalTypeResolver implements CompilerPass { private final AbstractCompiler compiler; GlobalTypeResolver(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { if (topScope == null) { typedScopeCreator = new MemoizedScopeCreator(new TypedScopeCreator(compiler)); topScope = typedScopeCreator.createScope(root.getParent(), null); } else { compiler.getTypeRegistry().resolveTypesInScope(topScope); } } } /** Checks global name usage. */ private final PassFactory checkGlobalNames = new PassFactory("Check names", true) { @Override protected CompilerPass createInternal(final AbstractCompiler compiler) { return new CompilerPass() { @Override public void process(Node externs, Node jsRoot) { // Create a global namespace for analysis by check passes. // Note that this class does all heavy computation lazily, // so it's OK to create it here. namespaceForChecks = new GlobalNamespace(compiler, jsRoot); new CheckGlobalNames(compiler, options.checkGlobalNamesLevel) .injectNamespace(namespaceForChecks).process(externs, jsRoot); } }; } }; /** Checks

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>_TYPE); if (anyObjectType.isSubtype(that)) { return anyObjectType; } else if (that.isSubtype(anyObjectType)) { return that; } else { return getNativeType(JSTypeNative.NO_TYPE); } } public JSType caseAllType() { return getNativeType(JSTypeNative.NO_OBJECT_TYPE); } public JSType caseVoidType() { return getNativeType(JSTypeNative.NO_OBJECT_TYPE); } public JSType caseEnumElementType(EnumElementType type) { return type.getPrimitiveType().visit(this); } } NoObjectType(JSTypeRegistry registry) { super(registry, null, null, registry.createArrowType(null, null), null, null, true, true); getInternalArrowType().returnType = this; this.setInstanceType(this); } @Override public TernaryValue testForEquality(JSType that) { return that.isEmptyType() ? TernaryValue.TRUE : TernaryValue.UNKNOWN; } @Override public boolean isSubtype(JSType that) { if (JSType.isSubtype(this, that)) { return true; } else { return that.isObject() && !that.isNoType(); } } @Override public boolean isFunctionType() { return false; } @Override public boolean isNoObjectType() { return true; } @Override public JSType getLeastSupertype(JSType that) { return that.visit(leastSupertypeVisitor); } @Override public JSType getGreatestSubtype(JSType that) { return that.visit(greatestSubtypeVisitor); } @Override public ObjectType getImplicitPrototype() { return null; } @Override public String getReferenceName() { return null; } @Override public boolean matchesNumberContext() { return true; } @Override public boolean matchesObjectContext() { return true; } @Override public boolean matchesStringContext() { return true; } @Override public boolean isEquivalentTo(JSType that) { return this == that; } @Override public int hashCode() { return System.identityHashCode(this); } @Override public int getPropertiesCount() { // Should never be called, returning the biggest number to highlight the // 'unifying' role of this type. return Integer.MAX_VALUE; } @Override public JSType getPropertyType(String propertyName) { // Return the least type to be a proper subtype of all other objects. return getNativeType(JSTypeNative.NO_TYPE); } @Override public boolean hasProperty(String propertyName) { // has all properties, since it is any object return true; } @Override boolean defineProperty(String propertyName, JSType type, boolean inferred, boolean inExterns) { // nothing, all properties are defined return true; } @Override public JSDocInfo getOwnPropertyJSDocInfo(String propertyName) { return null; } @Override public void setPropertyJSDocInfo(String propertyName, JSDocInfo info, boolean inExterns) { // Do nothing, specific properties

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>/* * Copyright 2007 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp.parsing; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.google.javascript.jscomp.mozilla.rhino.ErrorReporter; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.JSDocInfoBuilder; import com.google.javascript.rhino.JSTypeExpression; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.ScriptRuntime; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.JSDocInfo.Visibility; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * A parser for JSDoc comments. * */ // TODO(nicksantos): Unify all the JSDocInfo stuff into one package, instead of // spreading it across mutliple packages. public final class JsDocInfoParser { private final JsDocTokenStream stream; private final JSDocInfoBuilder jsdocBuilder; private final String sourceName; private final ErrorReporter errorReporter; private final ErrorReporterParser parser = new ErrorReporterParser(); private class ErrorReporterParser { void addWarning(String messageId, String messageArg, int lineno, int charno) { errorReporter.warning(ScriptRuntime.getMessage1(messageId, messageArg), sourceName, lineno, null, charno); } void addWarning(String messageId, int lineno, int charno) { errorReporter.warning(ScriptRuntime.getMessage0(messageId), sourceName, lineno, null, charno); } } // The DocInfo with the fileoverview tag for the whole file. private JSDocInfo fileOverviewJSDocInfo = null; private State state; private final Map<String, Annotation> annotationNames; private final Set<String> suppressionNames; static private final Set<String> modifiesAnnotationKeywords = ImmutableSet.<String>of("this", "arguments"); private Node.FileLevelJsDocBuilder fileLevelJsDocBuilder; /** * Sets the JsDocBuilder for the file-level (root) node of this parse. The * parser uses the builder to append any preserve annotations it encounters * in jsdoc comments. * * @param fileLevelJsDocBuilder */ void setFileLevelJsDocBuilder( Node.FileLevelJsDocBuilder fileLevelJsDocBuilder) { this.

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>fileLevelJsDocBuilder = fileLevelJsDocBuilder; } /** * Sets the file overview JSDocInfo, in order to warn about multiple uses of * the @fileoverview tag in a file. */ void setFileOverviewJSDocInfo(JSDocInfo fileOverviewJSDocInfo) { this.fileOverviewJSDocInfo = fileOverviewJSDocInfo; } private enum State { SEARCHING_ANNOTATION, SEARCHING_NEWLINE, NEXT_IS_ANNOTATION } JsDocInfoParser(JsDocTokenStream stream, String sourceName, Config config, ErrorReporter errorReporter) { this.stream = stream; this.sourceName = sourceName; this.jsdocBuilder = new JSDocInfoBuilder(config.parseJsDocDocumentation); this.annotationNames = config.annotationNames; this.suppressionNames = config.suppressionNames; this.errorReporter = errorReporter; } /** * Parses a string containing a JsDoc type declaration, returning the * type if the parsing succeeded or {@code null} if it failed. */ public static Node parseTypeString(String typeString) { Config config = new Config( Sets.<String>newHashSet(), Sets.<String>newHashSet(), false); JsDocInfoParser parser = new JsDocInfoParser( new JsDocTokenStream(typeString), "typeparsing", config, NullErrorReporter.forNewRhino()); return parser.parseTopLevelTypeExpression(parser.next()); } /** * Parses a {@link JSDocInfo} object. This parsing method reads all tokens * returned by the {@link JsDocTokenStream#getJsDocToken()} method until the * {@link JsDocToken#EOC} is returned. * * @return {@code true} if JSDoc information was correctly parsed, * {@code false} otherwise */ boolean parse() { int lineno; int charno; // JSTypes are represented as Rhino AST nodes, and then resolved later. JSTypeExpression type; state = State.SEARCHING_ANNOTATION; skipEOLs(); JsDocToken token = next(); // Always record that we have a comment. if (jsdocBuilder.shouldParseDocumentation()) { ExtractionInfo blockInfo = extractBlockComment(token); token = blockInfo.token; if (!blockInfo.string.isEmpty()) { jsdocBuilder.recordBlockDescription(blockInfo.string); } } else { if (token != JsDocToken.ANNOTATION && token != JsDocToken.EOC) { // Mark that there was a description, but don't bother marking // what it was. jsdocBuilder.recordBlockDescription(""); } } // Parse the actual JsDoc. retry: for (;;) { switch (token) { case ANNOTATION: if (state == State.SEARCHING_ANNOTATION) { state = State.SEARCHING_NEWLINE; lineno = stream.getLineno(); charno = stream.getCharno(); String annotationName = stream.getString(); Annotation annotation = annotationNames.get(annotationName); if (annotation == null) { parser.addWarning("msg.bad.jsdoc.tag", annotation

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>ips all EOLs and all empty lines in the JSDoc. Call this method if you * want the JSDoc entry to span multiple lines. */ private void skipEOLs() { while (match(JsDocToken.EOL)) { next(); if (match(JsDocToken.STAR)) { next(); } } } /** * Determines whether the parser has been populated with docinfo with a * fileoverview tag. */ private boolean hasParsedFileOverviewDocInfo() { return jsdocBuilder.isPopulatedWithFileOverview(); } boolean hasParsedJSDocInfo() { return jsdocBuilder.isPopulated(); } JSDocInfo retrieveAndResetParsedJSDocInfo() { return jsdocBuilder.build(sourceName); } /** * Gets the fileoverview JSDocInfo, if any. */ JSDocInfo getFileOverviewJSDocInfo() { return fileOverviewJSDocInfo; } }

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>/* * Copyright 2007 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.jstype.FunctionType; import com.google.javascript.rhino.jstype.JSTypeRegistry; import com.google.javascript.rhino.jstype.ObjectType; import java.util.Collection; import java.util.Collections; import java.util.List; /** * CodingConvention defines a set of hooks to customize the behavior of the * Compiler for a specific team/company. * */ public class DefaultCodingConvention implements CodingConvention { private static final long serialVersionUID = 1L; @Override public boolean isConstant(String variableName) { return false; } @Override public boolean isConstantKey(String variableName) { return false; } @Override public boolean isValidEnumKey(String key) { return key != null && key.length() > 0; } @Override public boolean isOptionalParameter(Node parameter) { // be as lax as possible, but this must be mutually exclusive from // var_args parameters. return !isVarArgsParameter(parameter); } @Override public boolean isVarArgsParameter(Node parameter) { // be as lax as possible return parameter.getParent().getLastChild() == parameter; } @Override public boolean isExported(String name, boolean local) { return local && name.startsWith("$super"); } @Override public boolean isExported(String name) { return isExported(name, false) || isExported(name, true); } @Override public boolean isPrivate(String name) { return false; } @Override public SubclassRelationship getClassesDefinedByCall(Node callNode) { return null; } @Override public boolean isSuperClassReference(String propertyName) { return false; } @Override public String extractClassNameIfProvide(Node node, Node parent) { String message = "only implemented in GoogleCodingConvention"; throw new UnsupportedOperationException(message); } @Override public String extractClassNameIfRequire(Node node, Node parent) { String message = "only implemented in GoogleCodingConvention"; throw new UnsupportedOperationException(message); } @Override public String getExportPropertyFunction() { return null; } @Override public String getExportSymbolFunction() { return null; } @Override public List<String> identifyTypeDeclarationCall(Node n) { return null; } @Override public String identifyTypeDefAssign(Node n) { return null; } @Override public

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>/* * Copyright 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.collect.Maps; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.util.Map; /** * Filters warnings based on in-code {@code @suppress} annotations. * @author nicksantos@google.com (Nick Santos) */ class SuppressDocWarningsGuard extends WarningsGuard { /** Warnings guards for each suppressable warnings group, indexed by name. */ private final Map<String, DiagnosticGroupWarningsGuard> suppressors = Maps.newHashMap(); /** * The suppressable groups, indexed by name. */ SuppressDocWarningsGuard(Map<String, DiagnosticGroup> suppressableGroups) { for (Map.Entry<String, DiagnosticGroup> entry : suppressableGroups.entrySet()) { suppressors.put( entry.getKey(), new DiagnosticGroupWarningsGuard( entry.getValue(), CheckLevel.OFF)); } } @Override public CheckLevel level(JSError error) { Node node = error.node; if (node != null) { for (Node current = node; current != null; current = current.getParent()) { int type = current.getType(); JSDocInfo info = null; // We only care about function annotations at the FUNCTION and SCRIPT // level. Otherwise, the @suppress annotation has an implicit // dependency on the exact structure of our AST, and that seems like // a bad idea. if (type == Token.FUNCTION) { info = NodeUtil.getFunctionInfo(current); } else if (type == Token.SCRIPT) { info = current.getJSDocInfo(); } if (info != null) { for (String suppressor : info.getSuppressions()) { WarningsGuard guard = suppressors.get(suppressor); // Some @suppress tags are for other tools, and // may not have a warnings guard. if (guard != null) { CheckLevel newLevel = guard.level(error); if (newLevel != null) { return newLevel; } } } } } } return null; } @Override public int getPriority() { // Happens after path-based filtering, but before other times // of filtering. return WarningsGuard.Priority.SUPPRESS_DOC.value; } }

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>/* * Copyright 2004 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.TokenStream; import com.google.javascript.rhino.jstype.TernaryValue; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; /** * NodeUtil contains utilities that get properties from the Node object. * */ public final class NodeUtil { // TODO(user): Eliminate this class and make all of the static methods // instance methods of com.google.javascript.rhino.Node. /** the set of builtin constructors that don't have side effects. */ private static final Set<String> CONSTRUCTORS_WITHOUT_SIDE_EFFECTS = new HashSet<String>(Arrays.asList( "Array", "Date", "Error", "Object", "RegExp", "XMLHttpRequest")); // Utility class; do not instantiate. private NodeUtil() {} /** * Gets the boolean value of a node that represents a expression. This method * effectively emulates the <code>Boolean()</code> JavaScript cast function. * Note: unlike getBooleanValue this function does not return UNKNOWN * for expressions with side-effects. */ static TernaryValue getExpressionBooleanValue(Node n) { switch (n.getType()) { case Token.ASSIGN: case Token.COMMA: // For ASSIGN and COMMA the value is the value of the RHS. return getExpressionBooleanValue(n.getLastChild()); case Token.NOT: TernaryValue value = getExpressionBooleanValue(n.getLastChild()); return value.not(); case Token.AND: { TernaryValue lhs = getExpressionBooleanValue(n.getFirstChild()); TernaryValue rhs = getExpressionBooleanValue(n.getLastChild()); return lhs.and(rhs); } case Token.OR: { TernaryValue lhs = getExpressionBooleanValue(n.getFirstChild()); TernaryValue rhs = getExpressionBooleanValue(n.getLastChild

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> node whose type is {@link Token#FUNCTION} * @return the function's name, or {@code null} if it has no name */ static String getFunctionName(Node n) { Node parent = n.getParent(); String name = n.getFirstChild().getString(); switch (parent.getType()) { case Token.NAME: // var name = function() ... // var name2 = function name1() ... return parent.getString(); case Token.ASSIGN: // qualified.name = function() ... // qualified.name2 = function name1() ... return parent.getFirstChild().getQualifiedName(); default: // function name() ... return name != null && name.length() != 0 ? name : null; } } /** * Gets the function's name. This method recognizes the forms: * <ul> * <li>{@code {'name': function() ...}}</li> * <li>{@code {name: function() ...}}</li> * <li>{@code function name() ...}</li> * <li>{@code var name = function() ...}</li> * <li>{@code qualified.name = function() ...}</li> * <li>{@code var name2 = function name1() ...}</li> * <li>{@code qualified.name2 = function name1() ...}</li> * </ul> * * @param n a node whose type is {@link Token#FUNCTION} * @return the function's name, or {@code null} if it has no name */ static String getNearestFunctionName(Node n) { String name = getFunctionName(n); if (name != null) { return name; } // Check for the form { 'x' : function() { } } Node parent = n.getParent(); switch (parent.getType()) { case Token.STRING: // Return the name of the literal's key. return getStringValue(parent); } return null; } /** * Returns true if this is an immutable value. */ static boolean isImmutableValue(Node n) { switch (n.getType()) { case Token.STRING: case Token.NUMBER: case Token.NULL: case Token.TRUE: case Token.FALSE: return true; case Token.VOID: case Token.NEG: return isImmutableValue(n.getFirstChild()); case Token.NAME: String name = n.getString(); // We assume here that programs don't change the value of the keyword // undefined to something other than the value undefined. return "undefined".equals(name) || "Infinity".equals(name) || "NaN".equals(name); } return false; } /** * Returns true if this is a literal value. We define a literal value * as any node that evaluates to the same thing regardless of when or * where it is evaluated. So /xyz/ and [3, 5] are literals, but * the name a is not. * * Function literals do not meet this definition, because they * lexically capture variables. For example, if you have * <code> * function() { return a; } * </code> * If it is evaluated in a different scope, then it * captures a different variable. Even if the function did

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> not read * any captured vairables directly, it would still fail this definition, * because it affects the lifecycle of variables in the enclosing scope. * * However, a function literal with respect to a particular scope is * a literal. * * @param includeFunctions If true, all function expressions will be * treated as literals. */ static boolean isLiteralValue(Node n, boolean includeFunctions) { switch (n.getType()) { case Token.ARRAYLIT: case Token.REGEXP: // Return true only if all children are const. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!isLiteralValue(child, includeFunctions)) { return false; } } return true; case Token.OBJECTLIT: // Return true only if all values are const. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!isLiteralValue(child.getFirstChild(), includeFunctions)) { return false; } } return true; case Token.FUNCTION: return includeFunctions && !NodeUtil.isFunctionDeclaration(n); default: return isImmutableValue(n); } } /** * Determines whether the given value may be assigned to a define. * * @param val The value being assigned. * @param defines The list of names of existing defines. */ static boolean isValidDefineValue(Node val, Set<String> defines) { switch (val.getType()) { case Token.STRING: case Token.NUMBER: case Token.TRUE: case Token.FALSE: return true; // Binary operators are only valid if both children are valid. case Token.ADD: case Token.BITAND: case Token.BITNOT: case Token.BITOR: case Token.BITXOR: case Token.DIV: case Token.EQ: case Token.GE: case Token.GT: case Token.LE: case Token.LSH: case Token.LT: case Token.MOD: case Token.MUL: case Token.NE: case Token.RSH: case Token.SHEQ: case Token.SHNE: case Token.SUB: case Token.URSH: return isValidDefineValue(val.getFirstChild(), defines) && isValidDefineValue(val.getLastChild(), defines); // Uniary operators are valid if the child is valid. case Token.NOT: case Token.NEG: case Token.POS: return isValidDefineValue(val.getFirstChild(), defines); // Names are valid if and only if they are defines themselves. case Token.NAME: case Token.GETPROP: if (val.isQualifiedName()) { return defines.contains(val.getQualifiedName()); } } return false; } /** * Returns whether this a BLOCK node with no children. * * @param block The node. */ static boolean isEmptyBlock(Node block) { if (block.getType() != Token.BLOCK) { return false; } for (Node n = block.getFirstChild(); n != null; n = n.getNext()) { if (n.getType() != Token.EMPTY) { return false; } }

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>.GETPROP) { if (callNode.isOnlyModifiesThisCall() && evaluatesToLocalValue(nameNode.getFirstChild())) { return false; } // Functions in the "Math" namespace have no side effects. if (nameNode.getFirstChild().getType() == Token.NAME) { String namespaceName = nameNode.getFirstChild().getString(); if (namespaceName.equals("Math")) { return false; } } if (compiler != null && !compiler.hasRegExpGlobalReferences()) { if (nameNode.getFirstChild().getType() == Token.REGEXP && REGEXP_METHODS.contains(nameNode.getLastChild().getString())) { return false; } else if (nameNode.getFirstChild().getType() == Token.STRING && STRING_REGEXP_METHODS.contains( nameNode.getLastChild().getString())) { Node param = nameNode.getNext(); if (param != null && (param.getType() == Token.STRING || param.getType() == Token.REGEXP)) return false; } } } return true; } /** * @return Whether the call has a local result. */ static boolean callHasLocalResult(Node n) { Preconditions.checkState(n.getType() == Token.CALL); return (n.getSideEffectFlags() & Node.FLAG_LOCAL_RESULTS) > 0; } /** * Returns true if the current node's type implies side effects. * * This is a non-recursive version of the may have side effects * check; used to check wherever the current node's type is one of * the reason's why a subtree has side effects. */ static boolean nodeTypeMayHaveSideEffects(Node n) { return nodeTypeMayHaveSideEffects(n, null); } static boolean nodeTypeMayHaveSideEffects(Node n, AbstractCompiler compiler) { if (isAssignmentOp(n)) { return true; } switch(n.getType()) { case Token.DELPROP: case Token.DEC: case Token.INC: case Token.THROW: return true; case Token.CALL: return NodeUtil.functionCallHasSideEffects(n, compiler); case Token.NEW: return NodeUtil.constructorCallHasSideEffects(n, compiler); case Token.NAME: // A variable definition. return n.hasChildren(); default: return false; } } /** * @return Whether the tree can be affected by side-effects or * has side-effects. */ static boolean canBeSideEffected(Node n) { Set<String> emptySet = Collections.emptySet(); return canBeSideEffected(n, emptySet); } /** * @param knownConstants A set of names known to be constant value at * node 'n' (such as locals that are last written before n can execute). * @return Whether the tree can be affected by side-effects or * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> referencesThis(Node n) { return containsType(n, Token.THIS, new MatchNotFunction()); } /** * Is this a GETPROP or GETELEM node? */ static boolean isGet(Node n) { return n.getType() == Token.GETPROP || n.getType() == Token.GETELEM; } /** * Is this a GETPROP node? */ static boolean isGetProp(Node n) { return n.getType() == Token.GETPROP; } /** * Is this a NAME node? */ static boolean isName(Node n) { return n.getType() == Token.NAME; } /** * Is this a NEW node? */ static boolean isNew(Node n) { return n.getType() == Token.NEW; } /** * Is this a VAR node? */ static boolean isVar(Node n) { return n.getType() == Token.VAR; } /** * Is this node the name of a variable being declared? * * @param n The node * @return True if {@code n} is NAME and {@code parent} is VAR */ static boolean isVarDeclaration(Node n) { // There is no need to verify that parent != null because a NAME node // always has a parent in a valid parse tree. return n.getType() == Token.NAME && n.getParent().getType() == Token.VAR; } /** * For an assignment or variable declaration get the assigned value. * @return The value node representing the new value. */ static Node getAssignedValue(Node n) { Preconditions.checkState(isName(n)); Node parent = n.getParent(); if (isVar(parent)) { return n.getFirstChild(); } else if (isAssign(parent) && parent.getFirstChild() == n) { return n.getNext(); } else { return null; } } /** * Is this a STRING node? */ static boolean isString(Node n) { return n.getType() == Token.STRING; } /** * Is this node an assignment expression statement? * * @param n The node * @return True if {@code n} is EXPR_RESULT and {@code n}'s * first child is ASSIGN */ static boolean isExprAssign(Node n) { return n.getType() == Token.EXPR_RESULT && n.getFirstChild().getType() == Token.ASSIGN; } /** * Is this an ASSIGN node? */ static boolean isAssign(Node n) { return n.getType() == Token.ASSIGN; } /** * Is this node a call expression statement? * * @param n The node * @return True if {@code n} is EXPR_RESULT and {@code n}'s * first child is CALL */ static boolean isExprCall(Node n) { return n.getType() == Token.EXPR_RESULT && n.getFirstChild().getType() == Token.CALL; } /** * @return Whether the node represents a FOR-IN loop. */ static boolean isForIn(Node n) { return n.getType() == Token.FOR && n.getChildCount() == 3;

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> } /** * Determines whether the given node is a FOR, DO, or WHILE node. */ static boolean isLoopStructure(Node n) { switch (n.getType()) { case Token.FOR: case Token.DO: case Token.WHILE: return true; default: return false; } } /** * @param n The node to inspect. * @return If the node, is a FOR, WHILE, or DO, it returns the node for * the code BLOCK, null otherwise. */ static Node getLoopCodeBlock(Node n) { switch (n.getType()) { case Token.FOR: case Token.WHILE: return n.getLastChild(); case Token.DO: return n.getFirstChild(); default: return null; } } /** * @return Whether the specified node has a loop parent that * is within the current scope. */ static boolean isWithinLoop(Node n) { for (Node parent : n.getAncestors()) { if (NodeUtil.isLoopStructure(parent)) { return true; } if (NodeUtil.isFunction(parent)) { break; } } return false; } /** * Determines whether the given node is a FOR, DO, WHILE, WITH, or IF node. */ static boolean isControlStructure(Node n) { switch (n.getType()) { case Token.FOR: case Token.DO: case Token.WHILE: case Token.WITH: case Token.IF: case Token.LABEL: case Token.TRY: case Token.CATCH: case Token.SWITCH: case Token.CASE: case Token.DEFAULT: return true; default: return false; } } /** * Determines whether the given node is code node for FOR, DO, * WHILE, WITH, or IF node. */ static boolean isControlStructureCodeBlock(Node parent, Node n) { switch (parent.getType()) { case Token.FOR: case Token.WHILE: case Token.LABEL: case Token.WITH: return parent.getLastChild() == n; case Token.DO: return parent.getFirstChild() == n; case Token.IF: return parent.getFirstChild() != n; case Token.TRY: return parent.getFirstChild() == n || parent.getLastChild() == n; case Token.CATCH: return parent.getLastChild() == n; case Token.SWITCH: case Token.CASE: return parent.getFirstChild() != n; case Token.DEFAULT: return true; default: Preconditions.checkState(isControlStructure(parent)); return false; } } /** * Gets the condition of an ON_TRUE / ON_FALSE CFG edge. * @param n a node with an outgoing conditional CFG edge * @return the condition node or null if the condition is not obviously a node */ static Node getConditionExpression(Node n) { switch (n.getType()) { case Token.IF: case Token.WHILE: return n.getFirstChild(); case Token.DO: return n.getLastChild(); case Token.FOR: switch (n.getChildCount()) { case

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> 3: return null; case 4: return n.getFirstChild().getNext(); } throw new IllegalArgumentException("malformed 'for' statement " + n); case Token.CASE: return null; } throw new IllegalArgumentException(n + " does not have a condition."); } /** * @return Whether the node is of a type that contain other statements. */ static boolean isStatementBlock(Node n) { return n.getType() == Token.SCRIPT || n.getType() == Token.BLOCK; } /** * @return Whether the node is used as a statement. */ static boolean isStatement(Node n) { Node parent = n.getParent(); // It is not possible to determine definitely if a node is a statement // or not if it is not part of the AST. A FUNCTION node can be // either part of an expression or a statement. Preconditions.checkState(parent != null); switch (parent.getType()) { case Token.SCRIPT: case Token.BLOCK: case Token.LABEL: return true; default: return false; } } /** Whether the node is part of a switch statement. */ static boolean isSwitchCase(Node n) { return n.getType() == Token.CASE || n.getType() == Token.DEFAULT; } /** * @return Whether the name is a reference to a variable, function or * function parameter (not a label or a empty function expression name). */ static boolean isReferenceName(Node n) { return isName(n) && !n.getString().isEmpty(); } /** @return Whether the node is a label name. */ static boolean isLabelName(Node n) { return (n != null && n.getType() == Token.LABEL_NAME); } /** Whether the child node is the FINALLY block of a try. */ static boolean isTryFinallyNode(Node parent, Node child) { return parent.getType() == Token.TRY && parent.getChildCount() == 3 && child == parent.getLastChild(); } /** Safely remove children while maintaining a valid node structure. */ static void removeChild(Node parent, Node node) { // Node parent = node.getParent(); if (isStatementBlock(parent) || isSwitchCase(node) || isTryFinallyNode(parent, node)) { // A statement in a block can simply be removed. parent.removeChild(node); } else if (parent.getType() == Token.VAR) { if (parent.hasMoreThanOneChild()) { parent.removeChild(node); } else { // Remove the node from the parent, so it can be reused. parent.removeChild(node); // This would leave an empty VAR, remove the VAR itself. removeChild(parent.getParent(), parent); } } else if (node.getType() == Token.BLOCK) { // Simply empty the block. This maintains source location and // "synthetic"-ness. node.detachChildren(); } else if (parent.getType() == Token.LABEL && node == parent.getLastChild()) { // Remove the node from the parent, so it can be reused. parent.removeChild(node); // A LABEL without children can not be referred to, remove it. removeChild(parent

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>.getParent(), parent); } else if (parent.getType() == Token.FOR && parent.getChildCount() == 4) { // Only Token.FOR can have an Token.EMPTY other control structure // need something for the condition. Others need to be replaced // or the structure removed. parent.replaceChild(node, new Node(Token.EMPTY)); } else { throw new IllegalStateException("Invalid attempt to remove node: " + node.toString() + " of "+ parent.toString()); } } /** * Merge a block with its parent block. * @return Whether the block was removed. */ static boolean tryMergeBlock(Node block) { Preconditions.checkState(block.getType() == Token.BLOCK); Node parent = block.getParent(); // Try to remove the block if its parent is a block/script or if its // parent is label and it has exactly one child. if (isStatementBlock(parent)) { Node previous = block; while (block.hasChildren()) { Node child = block.removeFirstChild(); parent.addChildAfter(child, previous); previous = child; } parent.removeChild(block); return true; } else { return false; } } /** * Is this a CALL node? */ static boolean isCall(Node n) { return n.getType() == Token.CALL; } /** * Is this a FUNCTION node? */ static boolean isFunction(Node n) { return n.getType() == Token.FUNCTION; } /** * Return a BLOCK node for the given FUNCTION node. */ static Node getFunctionBody(Node fn) { Preconditions.checkArgument(isFunction(fn)); return fn.getLastChild(); } /** * Is this a THIS node? */ static boolean isThis(Node node) { return node.getType() == Token.THIS; } /** * Is this node or any of its children a CALL? */ static boolean containsCall(Node n) { return containsType(n, Token.CALL); } /** * Is this node a function declaration? A function declaration is a function * that has a name that is added to the current scope (i.e. a function that * is not part of a expression; see {@link #isFunctionExpression}). */ static boolean isFunctionDeclaration(Node n) { return n.getType() == Token.FUNCTION && isStatement(n); } /** * Is this node a hoisted function declaration? A function declaration in the * scope root is hoisted to the top of the scope. * See {@link #isFunctionDeclaration}). */ static boolean isHoistedFunctionDeclaration(Node n) { return isFunctionDeclaration(n) && (n.getParent().getType() == Token.SCRIPT || n.getParent().getParent().getType() == Token.FUNCTION); } /** * Is a FUNCTION node an function expression? An function expression is one * that has either no name or a name that is not added to the current scope. * * <p>Some examples of function expressions: * <pre> * (function () {}) * (function f() {})() * [ function f() {} ] * var f = function f() {}; * for (function f() {};;)

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>, Node parent) { return (parent.getType() == Token.ASSIGN && parent.getFirstChild() == n) || parent.getType() == Token.VAR; } /** * Determines whether a node represents an object literal key * (e.g. key1 in {key1: value1, key2: value2}). * * @param node A node * @param parent The node's parent */ static boolean isObjectLitKey(Node node, Node parent) { // TODO(nicksantos): What about NUMBER? return node.getType() == Token.STRING && parent.getType() == Token.OBJECTLIT; } /** * Converts an operator's token value (see {@link Token}) to a string * representation. * * @param operator the operator's token value to convert * @return the string representation or {@code null} if the token value is * not an operator */ static String opToStr(int operator) { switch (operator) { case Token.BITOR: return "|"; case Token.OR: return "||"; case Token.BITXOR: return "^"; case Token.AND: return "&&"; case Token.BITAND: return "&"; case Token.SHEQ: return "==="; case Token.EQ: return "=="; case Token.NOT: return "!"; case Token.NE: return "!="; case Token.SHNE: return "!=="; case Token.LSH: return "<<"; case Token.IN: return "in"; case Token.LE: return "<="; case Token.LT: return "<"; case Token.URSH: return ">>>"; case Token.RSH: return ">>"; case Token.GE: return ">="; case Token.GT: return ">"; case Token.MUL: return "*"; case Token.DIV: return "/"; case Token.MOD: return "%"; case Token.BITNOT: return "~"; case Token.ADD: return "+"; case Token.SUB: return "-"; case Token.POS: return "+"; case Token.NEG: return "-"; case Token.ASSIGN: return "="; case Token.ASSIGN_BITOR: return "|="; case Token.ASSIGN_BITXOR: return "^="; case Token.ASSIGN_BITAND: return "&="; case Token.ASSIGN_LSH: return "<<="; case Token.ASSIGN_RSH: return ">>="; case Token.ASSIGN_URSH: return ">>>="; case Token.ASSIGN_ADD: return "+="; case Token.ASSIGN_SUB: return "-="; case Token.ASSIGN_MUL: return "*="; case Token.ASSIGN_DIV: return "/="; case Token.ASSIGN_MOD: return "%="; case Token.VOID: return "void"; case Token.TYPEOF: return "typeof"; case Token.INSTANCEOF: return "instanceof"; default: return null; } } /** * Converts an operator's token value (see {@link Token}) to a string * representation or fails. * * @param operator the operator's token value to convert * @return the string representation * @throws Error if the token value is not an operator */ static String opToStrNoFail(

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>int operator) { String res = opToStr(operator); if (res == null) { throw new Error("Unknown op " + operator + ": " + Token.name(operator)); } return res; } /** * @return true if n or any of its children are of the specified type */ static boolean containsType(Node node, int type, Predicate<Node> traverseChildrenPred) { return has(node, new MatchNodeType(type), traverseChildrenPred); } /** * @return true if n or any of its children are of the specified type */ static boolean containsType(Node node, int type) { return containsType(node, type, Predicates.<Node>alwaysTrue()); } /** * Given a node tree, finds all the VAR declarations in that tree that are * not in an inner scope. Then adds a new VAR node at the top of the current * scope that redeclares them, if necessary. */ static void redeclareVarsInsideBranch(Node branch) { Collection<Node> vars = getVarsDeclaredInBranch(branch); if (vars.isEmpty()) { return; } Node parent = getAddingRoot(branch); for (Node nameNode : vars) { Node var = new Node( Token.VAR, Node.newString(Token.NAME, nameNode.getString()) .copyInformationFrom(nameNode)) .copyInformationFrom(nameNode); copyNameAnnotations(nameNode, var.getFirstChild()); parent.addChildToFront(var); } } /** * Copy any annotations that follow a named value. * @param source * @param destination */ static void copyNameAnnotations(Node source, Node destination) { if (source.getBooleanProp(Node.IS_CONSTANT_NAME)) { destination.putBooleanProp(Node.IS_CONSTANT_NAME, true); } } /** * Gets a Node at the top of the current scope where we can add new var * declarations as children. */ private static Node getAddingRoot(Node n) { Node addingRoot = null; Node ancestor = n; while (null != (ancestor = ancestor.getParent())) { int type = ancestor.getType(); if (type == Token.SCRIPT) { addingRoot = ancestor; break; } else if (type == Token.FUNCTION) { addingRoot = ancestor.getLastChild(); break; } } // make sure that the adding root looks ok Preconditions.checkState(addingRoot.getType() == Token.BLOCK || addingRoot.getType() == Token.SCRIPT); Preconditions.checkState(addingRoot.getFirstChild() == null || addingRoot.getFirstChild().getType() != Token.SCRIPT); return addingRoot; } /** Creates function name(params_0, ..., params_n) { body }. */ public static Node newFunctionNode(String name, List<Node> params, Node body, int lineno, int charno) { Node parameterParen = new Node(Token.LP, lineno, charno); for (Node param : params) { parameterParen.addChildToBack(param); } Node function = new Node(Token.FUNCTION, lineno, charno); function.addChildrenToBack( Node.newString(Token.NAME,

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> * * @param name The name for the new NAME node. * @param basisNode The node that represents the name as currently found in * the AST. * * @return The node created. */ static Node newName(String name, Node basisNode) { Node nameNode = Node.newString(Token.NAME, name); nameNode.copyInformationFrom(basisNode); return nameNode; } /** * Creates a new node representing an *existing* name, copying over the source * location information from the basis node and assigning the given original * name to the node. * * @param name The name for the new NAME node. * @param basisNode The node that represents the name as currently found in * the AST. * @param originalName The original name of the item being represented by the * NAME node. Used for debugging information. * * @return The node created. */ static Node newName(String name, Node basisNode, String originalName) { Node nameNode = newName(name, basisNode); nameNode.putProp(Node.ORIGINALNAME_PROP, originalName); return nameNode; } /** Test if all characters in the string are in the Basic Latin (aka ASCII) * character set - that they have UTF-16 values equal to or below 0x7f. * This check can find which identifiers with Unicode characters need to be * escaped in order to allow resulting files to be processed by non-Unicode * aware UNIX tools and editors. * * * See http://en.wikipedia.org/wiki/Latin_characters_in_Unicode * for more on Basic Latin. * * @param s The string to be checked for ASCII-goodness. * * @return True if all characters in the string are in Basic Latin set. */ static boolean isLatin(String s) { char LARGEST_BASIC_LATIN = 0x7f; int len = s.length(); for (int index = 0; index < len; index++) { char c = s.charAt(index); if (c > LARGEST_BASIC_LATIN) { return false; } } return true; } /** * Determines whether the given name can appear on the right side of * the dot operator. Many properties (like reserved words) cannot. */ static boolean isValidPropertyName(String name) { return TokenStream.isJSIdentifier(name) && !TokenStream.isKeyword(name) && // no Unicode escaped characters - some browsers are less tolerant // of Unicode characters that might be valid according to the // language spec. // Note that by this point, unicode escapes have been converted // to UTF-16 characters, so we're only searching for character // values, not escapes. isLatin(name); } private static class VarCollector implements Visitor { final Map<String, Node> vars = Maps.newLinkedHashMap(); public void visit(Node n) { if (n.getType() == Token.NAME) { Node parent = n.getParent(); if (parent != null && parent.getType() == Token.VAR) { String name = n.getString(); if (!vars.containsKey(name)) { vars

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> = type; } public boolean apply(Node n) { return n.getType() == type; } } /** * A predicate for matching var or function declarations. */ static class MatchDeclaration implements Predicate<Node> { public boolean apply(Node n) { return isFunctionDeclaration(n) || n.getType() == Token.VAR; } } /** * A predicate for matching anything except function nodes. */ static class MatchNotFunction implements Predicate<Node>{ public boolean apply(Node n) { return !isFunction(n); } } /** * A predicate for matching statements without exiting the current scope. */ static class MatchShallowStatement implements Predicate<Node>{ public boolean apply(Node n) { Node parent = n.getParent(); return n.getType() == Token.BLOCK || (!isFunction(n) && (parent == null || isControlStructure(parent) || isStatementBlock(parent))); } } /** * Finds the number of times a type is referenced within the node tree. */ static int getNodeTypeReferenceCount( Node node, int type, Predicate<Node> traverseChildrenPred) { return getCount(node, new MatchNodeType(type), traverseChildrenPred); } /** * Whether a simple name is referenced within the node tree. */ static boolean isNameReferenced(Node node, String name, Predicate<Node> traverseChildrenPred) { return has(node, new MatchNameNode(name), traverseChildrenPred); } /** * Whether a simple name is referenced within the node tree. */ static boolean isNameReferenced(Node node, String name) { return isNameReferenced(node, name, Predicates.<Node>alwaysTrue()); } /** * Finds the number of times a simple name is referenced within the node tree. */ static int getNameReferenceCount(Node node, String name) { return getCount( node, new MatchNameNode(name), Predicates.<Node>alwaysTrue()); } /** * @return Whether the predicate is true for the node or any of its children. */ static boolean has(Node node, Predicate<Node> pred, Predicate<Node> traverseChildrenPred) { if (pred.apply(node)) { return true; } if (!traverseChildrenPred.apply(node)) { return false; } for (Node c = node.getFirstChild(); c != null; c = c.getNext()) { if (has(c, pred, traverseChildrenPred)) { return true; } } return false; } /** * @return The number of times the the predicate is true for the node * or any of its children. */ static int getCount( Node n, Predicate<Node> pred, Predicate<Node> traverseChildrenPred) { int total = 0; if (pred.apply(n)) { total++; } if (traverseChildrenPred.apply(n)) { for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { total += getCount(c, pred, traverseChildrenPred); } } return total; } /** * Interface for use with the visit method. * @see #visit */ static interface Visitor {

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> Node node, Node parent) { String name = node.getString(); if (parent.getType() == Token.GETPROP && node == parent.getLastChild()) { return convention.isConstantKey(name); } else if (isObjectLitKey(node, parent)) { return convention.isConstantKey(name); } else { return convention.isConstant(name); } } /** * @param nameNode A name node * @return The JSDocInfo for the name node */ static JSDocInfo getInfoForNameNode(Node nameNode) { JSDocInfo info = null; Node parent = null; if (nameNode != null) { info = nameNode.getJSDocInfo(); parent = nameNode.getParent(); } if (info == null && parent != null && ((parent.getType() == Token.VAR && parent.hasOneChild()) || parent.getType() == Token.FUNCTION)) { info = parent.getJSDocInfo(); } return info; } /** * Get the JSDocInfo for a function. */ static JSDocInfo getFunctionInfo(Node n) { Preconditions.checkState(n.getType() == Token.FUNCTION); JSDocInfo fnInfo = n.getJSDocInfo(); if (fnInfo == null && NodeUtil.isFunctionExpression(n)) { // Look for the info on other nodes. Node parent = n.getParent(); if (parent.getType() == Token.ASSIGN) { // on ASSIGNs fnInfo = parent.getJSDocInfo(); } else if (parent.getType() == Token.NAME) { // on var NAME = function() { ... }; fnInfo = parent.getParent().getJSDocInfo(); } } return fnInfo; } /** * @param n The node. * @return The source name property on the node or its ancestors. */ static String getSourceName(Node n) { String sourceName = null; while (sourceName == null && n != null) { sourceName = (String) n.getProp(Node.SOURCENAME_PROP); n = n.getParent(); } return sourceName; } /** * A new CALL node with the "FREE_CALL" set based on call target. */ static Node newCallNode(Node callTarget, Node... parameters) { boolean isFreeCall = isName(callTarget); Node call = new Node(Token.CALL, callTarget); call.putBooleanProp(Node.FREE_CALL, isFreeCall); for (Node parameter : parameters) { call.addChildToBack(parameter); } return call; } /** * @return Whether the node is known to be a value that is not referenced * elsewhere. */ static boolean evaluatesToLocalValue(Node value) { return evaluatesToLocalValue(value, Predicates.<Node>alwaysFalse()); } /** * @param locals A predicate to apply to unknown local values. * @return Whether the node is known to be a value that is not a reference * outside the expression scope. */ static boolean evaluatesToLocalValue(Node value, Predicate<Node> locals) { switch (value.getType()) { case

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> Token.ASSIGN: // A result that is aliased by a non-local name, is the effectively the // same as returning a non-local name, but this doesn't matter if the // value is immutable. return NodeUtil.isImmutableValue(value.getLastChild()) || (locals.apply(value) && evaluatesToLocalValue(value.getLastChild(), locals)); case Token.COMMA: return evaluatesToLocalValue(value.getLastChild(), locals); case Token.AND: case Token.OR: return evaluatesToLocalValue(value.getFirstChild(), locals) && evaluatesToLocalValue(value.getLastChild(), locals); case Token.HOOK: return evaluatesToLocalValue(value.getFirstChild().getNext(), locals) && evaluatesToLocalValue(value.getLastChild(), locals); case Token.INC: case Token.DEC: if (value.getBooleanProp(Node.INCRDECR_PROP)) { return evaluatesToLocalValue(value.getFirstChild(), locals); } else { return true; } case Token.THIS: return locals.apply(value); case Token.NAME: return isImmutableValue(value) || locals.apply(value); case Token.GETELEM: case Token.GETPROP: // There is no information about the locality of object properties. return locals.apply(value); case Token.CALL: return callHasLocalResult(value) || locals.apply(value); case Token.NEW: return true; case Token.FUNCTION: case Token.REGEXP: case Token.ARRAYLIT: case Token.OBJECTLIT: // Literals objects with non-literal children are allowed. return true; case Token.IN: // TODO(johnlenz): should IN operator be included in #isSimpleOperator? return true; default: // Other op force a local value: // x = '' + g (x is now an local string) // x -= g (x is now an local number) if (isAssignmentOp(value) || isSimpleOperator(value) || isImmutableValue(value)) { return true; } throw new IllegalStateException( "Unexpected expression node" + value + "\n parent:" + value.getParent()); } } }

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>/* * Copyright 2004 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Charsets; import com.google.common.base.Preconditions; import com.google.javascript.jscomp.NodeUtil.MatchNotFunction; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.TokenStream; import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; /** * CodeGenerator generates codes from a parse tree, sending it to the specified * CodeConsumer. * */ class CodeGenerator { private static final char[] HEX_CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; private final CodeConsumer cc; private final CharsetEncoder outputCharsetEncoder; CodeGenerator( CodeConsumer consumer, Charset outputCharset) { cc = consumer; if (outputCharset == null || outputCharset == Charsets.US_ASCII) { // If we want our default (pretending to be UTF-8, but escaping anything // outside of straight ASCII), then don't use the encoder, but // just special-case the code. This keeps the normal path through // the code identical to how it's been for years. this.outputCharsetEncoder = null; } else { this.outputCharsetEncoder = outputCharset.newEncoder(); } } CodeGenerator(CodeConsumer consumer) { this(consumer, null); } void add(String str) { cc.add(str); } private void addIdentifier(String identifier) { cc.addIdentifier(identifierEscape(identifier)); } void add(Node n) { add(n, Context.OTHER); } void add(Node n, Context context) { if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); addLeftExpr(first, p, context);

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> cc.addOp(opstr, true); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); // Handle associativity. // e.g. if the parse tree is a * (b * c), // we can simply generate a * b * c. if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { // Assignments are the only right-associative binary operators addExpr(last, p, rhsContext); } else { addExpr(last, p + 1, rhsContext); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { Preconditions.checkState(first.getNext().getType() == Token.BLOCK && !first.getNext().hasMoreThanOneChild()); Preconditions.checkState(childCount >= 2 && childCount <= 3); add("try"); add(first, Context.PRESERVE_BLOCK); // second child contains the catch block, or nothing if there // isn't a catch block Node catchblock = first.getNext().getFirstChild(); if (catchblock != null) { add(catchblock); } if (childCount == 3) { add("finally"); add(last, Context.PRESERVE_BLOCK); } break; } case Token.CATCH: Preconditions.checkState(childCount == 2); add("catch("); add(first); add(")"); add(last, Context.PRESERVE_BLOCK); break; case Token.THROW: Preconditions.checkState(childCount == 1); add("throw"); add(first); // Must have a ';' after a throw statement, otherwise safari can't // parse this. cc.endStatement(true); break; case Token.RETURN: add("return"); if (childCount == 1) { add(first); } else { Preconditions.checkState(childCount == 0); } cc.endStatement(); break; case Token.VAR: if (first != null) { add("var "); addList(first, false, getContextForNoInOperator(context)); } break; case Token.LABEL_NAME: Preconditions.checkState(!n.getString().isEmpty()); addIdentifier(n.getString()); break; case Token.NAME: if (first == null || first.getType() == Token.EMPTY) { addIdentifier(n.getString()); } else { Preconditions.checkState(childCount == 1); addIdentifier(n.getString()); cc.addOp("=", true); if (first.getType() == Token.COMMA) { addExpr(first, NodeUtil.precedence(Token.ASSIGN)); } else { // Add expression, consider nearby code at lowest level of // precedence. addExpr(first, 0, getContextForNoInOperator(context)); } } break; case Token.ARRAYLIT:

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> add("["); addList(first, (int[]) n.getProp(Node.SKIP_INDEXES_PROP)); add("]"); break; case Token.LP: add("("); addList(first); add(")"); break; case Token.COMMA: Preconditions.checkState(childCount == 2); addList(first, false, context); break; case Token.NUMBER: Preconditions.checkState( childCount == ((n.getParent() != null && n.getParent().getType() == Token.OBJECTLIT) ? 1 : 0)); cc.addNumber(n.getDouble()); break; case Token.TYPEOF: case Token.VOID: case Token.NOT: case Token.BITNOT: case Token.POS: case Token.NEG: { // All of these unary operators are right-associative Preconditions.checkState(childCount == 1); cc.addOp(NodeUtil.opToStrNoFail(type), false); addExpr(first, NodeUtil.precedence(type)); break; } case Token.HOOK: { Preconditions.checkState(childCount == 3); int p = NodeUtil.precedence(type); addLeftExpr(first, p + 1, context); cc.addOp("?", true); addExpr(first.getNext(), 1); cc.addOp(":", true); addExpr(last, 1); break; } case Token.REGEXP: if (first.getType() != Token.STRING || last.getType() != Token.STRING) { throw new Error("Expected children to be strings"); } String regexp = regexpEscape(first.getString(), outputCharsetEncoder); // I only use one .add because whitespace matters if (childCount == 2) { add(regexp + last.getString()); } else { Preconditions.checkState(childCount == 1); add(regexp); } break; case Token.GET_REF: add(first); break; case Token.REF_SPECIAL: Preconditions.checkState(childCount == 1); add(first); add("."); add((String) n.getProp(Node.NAME_PROP)); break; case Token.FUNCTION: if (n.getClass() != Node.class) { throw new Error("Unexpected Node subclass."); } Preconditions.checkState(childCount == 3); boolean funcNeedsParens = (context == Context.START_OF_EXPR); if (funcNeedsParens) { add("("); } add("function"); add(first); add(first.getNext()); add(last, Context.PRESERVE_BLOCK); cc.endFunction(context == Context.STATEMENT); if (funcNeedsParens) { add(")"); } break; case Token.SCRIPT: case Token.BLOCK: { if (n.getClass() != Node.class) { throw new Error("Unexpected Node subclass."); } boolean preserveBlock = context == Context.PRESERVE_BLOCK; if (preserveBlock) { cc.beginBlock(); } boolean preferLineBreaks = type == Token.SCRIPT || (type == Token.BLOCK

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> && !preserveBlock && n.getParent() != null && n.getParent().getType() == Token.SCRIPT); for (Node c = first; c != null; c = c.getNext()) { add(c, Context.STATEMENT); // VAR doesn't include ';' since it gets used in expressions if (c.getType() == Token.VAR) { cc.endStatement(); } if (c.getType() == Token.FUNCTION) { cc.maybeLineBreak(); } // Prefer to break lines in between top-level statements // because top level statements are more homogeneous. if (preferLineBreaks) { cc.notePreferredLineBreak(); } } if (preserveBlock) { cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT)); } break; } case Token.FOR: if (childCount == 4) { add("for("); if (first.getType() == Token.VAR) { add(first, Context.IN_FOR_INIT_CLAUSE); } else { addExpr(first, 0, Context.IN_FOR_INIT_CLAUSE); } add(";"); add(first.getNext()); add(";"); add(first.getNext().getNext()); add(")"); addNonEmptyStatement( last, getContextForNonEmptyExpression(context), false); } else { Preconditions.checkState(childCount == 3); add("for("); add(first); add("in"); add(first.getNext()); add(")"); addNonEmptyStatement( last, getContextForNonEmptyExpression(context), false); } break; case Token.DO: Preconditions.checkState(childCount == 2); add("do"); addNonEmptyStatement(first, Context.OTHER, false); add("while("); add(last); add(")"); cc.endStatement(); break; case Token.WHILE: Preconditions.checkState(childCount == 2); add("while("); add(first); add(")"); addNonEmptyStatement( last, getContextForNonEmptyExpression(context), false); break; case Token.EMPTY: Preconditions.checkState(childCount == 0); break; case Token.GETPROP: { Preconditions.checkState( childCount == 2, "Bad GETPROP: expected 2 children, but got %s", childCount); Preconditions.checkState( last.getType() == Token.STRING, "Bad GETPROP: RHS should be STRING"); boolean needsParens = (first.getType() == Token.NUMBER); if (needsParens) { add("("); } addLeftExpr(first, NodeUtil.precedence(type), context); if (needsParens) { add(")"); } add("."); addIdentifier(last.getString()); break; } case Token.GETELEM: Preconditions.checkState( childCount == 2, "Bad GETELEM: expected 2 children but got %s", childCount); addLeftExpr(first, NodeUtil.precedence(type), context); add("["); add(first.getNext()); add("]"); break

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>; case Token.WITH: Preconditions.checkState(childCount == 2); add("with("); add(first); add(")"); addNonEmptyStatement( last, getContextForNonEmptyExpression(context), false); break; case Token.INC: case Token.DEC: { Preconditions.checkState(childCount == 1); String o = type == Token.INC ? "++" : "--"; int postProp = n.getIntProp(Node.INCRDECR_PROP); // A non-zero post-prop value indicates a post inc/dec, default of zero // is a pre-inc/dec. if (postProp != 0) { addLeftExpr(first, NodeUtil.precedence(type), context); cc.addOp(o, false); } else { cc.addOp(o, false); add(first); } break; } case Token.CALL: // We have two special cases here: // 1) If the left hand side of the call is a direct reference to eval, // then it must have a DIRECT_EVAL annotation. If it does not, then // that means it was originally an indirect call to eval, and that // indirectness must be preserved. // 2) If the left hand side of the call is a property reference, // then the call must not a FREE_CALL annotation. If it does, then // that means it was originally an call without an explicit this and // that must be preserved. if (isIndirectEval(first) || n.getBooleanProp(Node.FREE_CALL) && NodeUtil.isGet(first)) { add("(0,"); addExpr(first, NodeUtil.precedence(Token.COMMA)); add(")"); } else { addLeftExpr(first, NodeUtil.precedence(type), context); } add("("); addList(first.getNext()); add(")"); break; case Token.IF: boolean hasElse = childCount == 3; boolean ambiguousElseClause = context == Context.BEFORE_DANGLING_ELSE && !hasElse; if (ambiguousElseClause) { cc.beginBlock(); } add("if("); add(first); add(")"); if (hasElse) { addNonEmptyStatement( first.getNext(), Context.BEFORE_DANGLING_ELSE, false); add("else"); addNonEmptyStatement( last, getContextForNonEmptyExpression(context), false); } else { addNonEmptyStatement(first.getNext(), Context.OTHER, false); Preconditions.checkState(childCount == 2); } if (ambiguousElseClause) { cc.endBlock(); } break; case Token.NULL: case Token.THIS: case Token.FALSE: case Token.TRUE: Preconditions.checkState(childCount == 0); add(Node.tokenToName(type)); break; case Token.CONTINUE: Preconditions.checkState(childCount <= 1); add("continue"); if (childCount == 1) { if (first.getType() != Token.LABEL_NAME) { throw new Error("Unexpected token type. Should be LABEL_NAME."); } add

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>(" "); add(first); } cc.endStatement(); break; case Token.DEBUGGER: Preconditions.checkState(childCount == 0); add("debugger"); cc.endStatement(); break; case Token.BREAK: Preconditions.checkState(childCount <= 1); add("break"); if (childCount == 1) { if (first.getType() != Token.LABEL_NAME) { throw new Error("Unexpected token type. Should be LABEL_NAME."); } add(" "); add(first); } cc.endStatement(); break; case Token.EXPR_VOID: throw new Error("Unexpected EXPR_VOID. Should be EXPR_RESULT."); case Token.EXPR_RESULT: Preconditions.checkState(childCount == 1); add(first, Context.START_OF_EXPR); cc.endStatement(); break; case Token.NEW: add("new "); int precedence = NodeUtil.precedence(type); // If the first child contains a CALL, then claim higher precedence // to force parentheses. Otherwise, when parsed, NEW will bind to the // first viable parentheses (don't traverse into functions). if (NodeUtil.containsType(first, Token.CALL, new MatchNotFunction())) { precedence = NodeUtil.precedence(first.getType()) + 1; } addExpr(first, precedence); // '()' is optional when no arguments are present Node next = first.getNext(); if (next != null) { add("("); addList(next); add(")"); } break; case Token.STRING: Preconditions.checkState( childCount == ((n.getParent() != null && n.getParent().getType() == Token.OBJECTLIT) ? 1 : 0), n.getParent().toStringTree()); add(jsString(n.getString(), outputCharsetEncoder)); break; case Token.DELPROP: Preconditions.checkState(childCount == 1); add("delete "); add(first); break; case Token.OBJECTLIT: { boolean needsParens = (context == Context.START_OF_EXPR); if (needsParens) { add("("); } add("{"); for (Node c = first; c != null; c = c.getNext()) { if (c != first) { cc.listSeparator(); } // Object literal property names don't have to be quoted if they are // not JavaScript keywords if (c.getType() == Token.STRING && !TokenStream.isKeyword(c.getString()) && TokenStream.isJSIdentifier(c.getString()) && // do not encode literally any non-literal characters that were // unicode escaped. NodeUtil.isLatin(c.getString())) { add(c.getString()); } else { addExpr(c, 1); } add(":"); addExpr(c.getFirstChild(), 1); } add("}"); if (needsParens) { add(")"); } break; } case Token.SWITCH: add("switch("); add(first); add(")"); cc.beginBlock(); addAllSiblings(first.getNext()); cc.end

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>.getType() == Token.EMPTY) { cc.endStatement(true); } else { add(nodeToProcess, context); // VAR doesn't include ';' since it gets used in expressions - so any // VAR in a statement context needs a call to endStatement() here. if (nodeToProcess.getType() == Token.VAR) { cc.endStatement(); } } } /** * @return Whether the Node is a DO or FUNCTION (with or without * labels). */ private boolean isOneExactlyFunctionOrDo(Node n) { if (n.getType() == Token.LABEL) { Node labeledStatement = n.getLastChild(); if (labeledStatement.getType() != Token.BLOCK) { return isOneExactlyFunctionOrDo(labeledStatement); } else { // For labels with block children, we need to ensure that a // labeled FUNCTION or DO isn't generated when extraneous BLOCKs // are skipped. if (getNonEmptyChildCount(n, 2) == 1) { return isOneExactlyFunctionOrDo(getFirstNonEmptyChild(n)); } else { // Either a empty statement or an block with more than one child, // way it isn't a FUNCTION or DO. return false; } } } else { return (n.getType() == Token.FUNCTION || n.getType() == Token.DO); } } /** * Adds a node at the left-hand side of an expression. Unlike * {@link #addExpr(Node,int)}, this preserves information about the context. * * The left side of an expression is special because in the JavaScript * grammar, certain tokens may be parsed differently when they are at * the beginning of a statement. For example, "{}" is parsed as a block, * but "{'x': 'y'}" is parsed as an object literal. */ void addLeftExpr(Node n, int minPrecedence, Context context) { addExpr(n, minPrecedence, context); } void addExpr(Node n, int minPrecedence) { addExpr(n, minPrecedence, Context.OTHER); } private void addExpr(Node n, int minPrecedence, Context context) { if ((NodeUtil.precedence(n.getType()) < minPrecedence) || ((context == Context.IN_FOR_INIT_CLAUSE) && (n.getType() == Token.IN))){ add("("); add(n, clearContextForNoInOperator(context)); add(")"); } else { add(n, context); } } void addList(Node firstInList) { addList(firstInList, true, Context.OTHER); } void addList(Node firstInList, boolean isArrayOrFunctionArgument) { addList(firstInList, isArrayOrFunctionArgument, Context.OTHER); } void addList(Node firstInList, boolean isArrayOrFunctionArgument, Context lhsContext) { for (Node n = firstInList; n != null; n = n.getNext()) { boolean isFirst = n == firstInList; if (isFirst) { addLeftExpr(n, isArrayOrFunctionArgument ? 1 : 0, lhsContext); } else { cc.listSeparator

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> String singlequoteEscape, String backslashEscape, CharsetEncoder outputCharsetEncoder) { StringBuilder sb = new StringBuilder(s.length() + 2); sb.append(quote); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); switch (c) { case '\n': sb.append("\\n"); break; case '\r': sb.append("\\r"); break; case '\t': sb.append("\\t"); break; case '\\': sb.append(backslashEscape); break; case '\"': sb.append(doublequoteEscape); break; case '\'': sb.append(singlequoteEscape); break; case '>': // Break --> into --\> or ]]> into ]]\> if (i >= 2 && ((s.charAt(i - 1) == '-' && s.charAt(i - 2) == '-') || (s.charAt(i - 1) == ']' && s.charAt(i - 2) == ']'))) { sb.append("\\>"); } else { sb.append(c); } break; case '<': // Break </script into <\/script final String END_SCRIPT = "/script"; if (s.regionMatches(true, i + 1, END_SCRIPT, 0, END_SCRIPT.length())) { sb.append("<\\"); } else { sb.append(c); } break; default: // If we're given an outputCharsetEncoder, then check if the // character can be represented in this character set. if (outputCharsetEncoder != null) { if (outputCharsetEncoder.canEncode(c)) { sb.append(c); } else { // Unicode-escape the character. appendHexJavaScriptRepresentation(sb, c); } } else { // No charsetEncoder provided - pass straight latin characters // through, and escape the rest. Doing the explicit character // check is measurably faster than using the CharsetEncoder. if (c > 0x1f && c <= 0x7f) { sb.append(c); } else { // Other characters can be misinterpreted by some js parsers, // or perhaps mangled by proxies along the way, // so we play it safe and unicode escape them. appendHexJavaScriptRepresentation(sb, c); } } } } sb.append(quote); return sb.toString(); } static String identifierEscape(String s) { // First check if escaping is needed at all -- in most cases it isn't. if (NodeUtil.isLatin(s)) { return s; } // Now going through the string to escape non-latin characters if needed. StringBuilder sb = new StringBuilder(); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); // Identifiers should always go to Latin1/ ASCII characters because // different browser's rules for valid identifier characters are // crazy. if (c > 0x1F && c < 0x7F) { sb.append(c); } else { appendHexJavaScriptRepresentation(sb, c); } } return

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> { return version == VERSION_DEFAULT || version >= VERSION_1_3; } static String getSourcePositionFromStack(int[] linep) { Context cx = getCurrentContext(); if (cx == null) return null; /** * A bit of a hack, but the only way to get filename and line * number from an enclosing frame. */ CharArrayWriter writer = new CharArrayWriter(); RuntimeException re = new RuntimeException(); re.printStackTrace(new PrintWriter(writer)); String s = writer.toString(); int open = -1; int close = -1; int colon = -1; for (int i=0; i < s.length(); i++) { char c = s.charAt(i); if (c == ':') colon = i; else if (c == '(') open = i; else if (c == ')') close = i; else if (c == '\n' && open != -1 && close != -1 && colon != -1 && open < colon && colon < close) { String fileStr = s.substring(open + 1, colon); if (!fileStr.endsWith(".java")) { String lineStr = s.substring(colon + 1, close); try { linep[0] = Integer.parseInt(lineStr); if (linep[0] < 0) { linep[0] = 0; } return fileStr; } catch (NumberFormatException e) { // fall through } } open = close = colon = -1; } } return null; } public final boolean isGeneratingDebugChanged() { return generatingDebugChanged; } /** * Add a name to the list of names forcing the creation of real * activation objects for functions. * * @param name the name of the object to add to the list */ public void addActivationName(String name) { if (sealed) onSealedMutation(); if (activationNames == null) activationNames = new Hashtable<Object, Object>(5); activationNames.put(name, name); } /** * Check whether the name is in the list of names of objects * forcing the creation of activation objects. * * @param name the name of the object to test * * @return true if an function activation object is needed. */ public final boolean isActivationNeeded(String name) { return activationNames != null && activationNames.containsKey(name); } /** * Remove a name from the list of names forcing the creation of real * activation objects for functions. * * @param name the name of the object to remove from the list */ public void removeActivationName(String name) { if (sealed) onSealedMutation(); if (activationNames != null) activationNames.remove(name); } private static String implementationVersion; private boolean sealed; private Object sealKey; // for Objects, Arrays to tag themselves as being printed out, // so they don't print themselves out recursively. // Use ObjToIntMap instead of java.util.HashSet for JDK 1.1 compatibility ObjToIntMap iterating; Object interpreterSecurityDomain; int version; private ErrorReporter errorReporter

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> boolean propIsDefined = false; if (nameIsDefined) { // if the ancestor of a property is defined, then let's check that // the property is also explicitly defined if it needs to be. propIsDefined = (!propertyMustBeInitializedByFullName(prop) || prop.globalSets + prop.localSets > 0); } validateName(prop, propIsDefined); checkDescendantNames(prop, propIsDefined); } } } private void validateName(Name name, boolean isDefined) { // If the name is not defined, emit warnings for each reference. While // we're looking through each reference, check all the module dependencies. Ref declaration = name.declaration; if (!isDefined) { if (declaration != null) { reportRefToUndefinedName(name, declaration); } } if (name.refs != null) { JSModuleGraph moduleGraph = compiler.getModuleGraph(); for (Ref ref : name.refs) { if (!isDefined) { reportRefToUndefinedName(name, ref); } else { if (declaration != null && ref.module != declaration.module && !moduleGraph.dependsOn(ref.module, declaration.module)) { reportBadModuleReference(name, ref); } } } } } private void reportBadModuleReference(Name name, Ref ref) { compiler.report( JSError.make(ref.sourceName, ref.node, STRICT_MODULE_DEP_QNAME, ref.module.getName(), name.declaration.module.getName(), name.fullName())); } private void reportRefToUndefinedName(Name name, Ref ref) { // grab the highest undefined ancestor to output in the warning message. while (name.parent != null && name.parent.globalSets + name.parent.localSets == 0) { name = name.parent; } // If this is an annotated EXPR-GET, don't do anything. Node parent = ref.node.getParent(); if (parent.getType() == Token.EXPR_RESULT) { JSDocInfo info = ref.node.getJSDocInfo(); if (info != null && info.hasTypedefType()) { return; } } compiler.report( JSError.make(ref.sourceName, ref.node, level, UNDEFINED_NAME_WARNING, name.fullName())); } /** * Checks whether the given name is a property, and whether that property * must be initialized with its full qualified name. */ private static boolean propertyMustBeInitializedByFullName(Name name) { // If an object literal in the global namespace is never aliased, // then all of its properties must be defined using its full qualified // name. This implies that its properties must all be in the global // namespace as well. // // The same is not true for FUNCTION and OTHER types, because their // implicit prototypes have properties that are not captured by the global // namespace. return name.parent != null && name.parent.aliasingGets == 0 && name.parent.type == Name.Type.OBJECTLIT; } }

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>; if (NodeUtil.isExprCall(parent)) { Node callee = node.getFirstChild(); if (callee != null && callee.getType() == Token.GETPROP) { String qualifiedName = callee.getQualifiedName(); if ((functionName).equals(qualifiedName)) { className = callee.getNext().getString(); } } } return className; } /** * Use closure's implementation. * @return closure's function name for exporting properties. */ @Override public String getExportPropertyFunction() { return "goog.exportProperty"; } /** * Use closure's implementation. * @return closure's function name for exporting symbols. */ @Override public String getExportSymbolFunction() { return "goog.exportSymbol"; } @Override public List<String> identifyTypeDeclarationCall(Node n) { Node callName = n.getFirstChild(); if ("goog.addDependency".equals(callName.getQualifiedName()) && n.getChildCount() >= 3) { Node typeArray = callName.getNext().getNext(); if (typeArray.getType() == Token.ARRAYLIT) { List<String> typeNames = Lists.newArrayList(); for (Node name = typeArray.getFirstChild(); name != null; name = name.getNext()) { if (name.getType() == Token.STRING) { typeNames.add(name.getString()); } } return typeNames; } } return null; } @Override public String identifyTypeDefAssign(Node n) { Node firstChild = n.getFirstChild(); int type = n.getType(); if (type == Token.ASSIGN) { if (TYPEDEF_NAME.equals(n.getLastChild().getQualifiedName())) { return firstChild.getQualifiedName(); } } else if (type == Token.VAR && firstChild.hasChildren()) { if (TYPEDEF_NAME.equals( firstChild.getFirstChild().getQualifiedName())) { return firstChild.getString(); } } return null; } @Override public String getAbstractMethodName() { return "goog.abstractMethod"; } @Override public String getSingletonGetterClassName(Node callNode) { Node callArg = callNode.getFirstChild(); String callName = callArg.getQualifiedName(); // Use both the original name and the post-CollapseProperties name. if (!("goog.addSingletonGetter".equals(callName) || "goog$addSingletonGetter".equals(callName)) || callNode.getChildCount() != 2) { return null; } return callArg.getNext().getQualifiedName(); } @Override public void applySingletonGetter(FunctionType functionType, FunctionType getterType, ObjectType objectType) { functionType.defineDeclaredProperty("getInstance", getterType, false); functionType.defineDeclaredProperty("instance_", objectType, false); } @Override public String getGlobalObject() { return "goog.global"; } private final Set<String> propertyTestFunctions = ImmutableSet.of( "goog.isDef", "goog.isNull", "goog.isDefAndNotNull", "goog.isString", "goog.isNumber", "goog.isBoolean", "goog.isFunction", "goog.isArray", "goog.

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> return lineno << COLUMN_BITS | (charno & COLUMN_MASK); } } /** * Extracts the line number and character number from a merged line char * number (see {@link #mergeLineCharNo(int, int)}). */ protected static int extractLineno(int lineCharNo) { if (lineCharNo == -1) { return -1; } else { return lineCharNo >>> COLUMN_BITS; } } /** * Extracts the character number and character number from a merged line * char number (see {@link #mergeLineCharNo(int, int)}). */ protected static int extractCharno(int lineCharNo) { if (lineCharNo == -1) { return -1; } else { return lineCharNo & COLUMN_MASK; } } //========================================================================== // Iteration /** * <p>Return an iterable object that iterates over this nodes's children. * The iterator does not support the optional operation * {@link Iterator#remove()}.</p> * * <p>To iterate over a node's siblings, one can write</p> * <pre>Node n = ...; * for (Node child : n.children()) { ...</pre> */ public Iterable<Node> children() { if (first == null) { return Collections.emptySet(); } else { return new SiblingNodeIterable(first); } } /** * <p>Return an iterable object that iterates over this nodes's siblings. * The iterator does not support the optional operation * {@link Iterator#remove()}.</p> * * <p>To iterate over a node's siblings, one can write</p> * <pre>Node n = ...; * for (Node sibling : n.siblings()) { ...</pre> */ public Iterable<Node> siblings() { return new SiblingNodeIterable(this); } /** * @see Node#siblings() */ private static final class SiblingNodeIterable implements Iterable<Node>, Iterator<Node> { private final Node start; private Node current; private boolean used; SiblingNodeIterable(Node start) { this.start = start; this.current = start; this.used = false; } public Iterator<Node> iterator() { if (!used) { used = true; return this; } else { // We have already used the current object as an iterator; // we must create a new SiblingNodeIterable based on this // iterable's start node. // // Since the primary use case for Node.children is in for // loops, this branch is extremely unlikely. return (new SiblingNodeIterable(start)).iterator(); } } public boolean hasNext() { return current != null; } public Node next() { if (current == null) { throw new NoSuchElementException(); } try { return current; } finally { current = current.getNext(); } } public void remove() { throw new UnsupportedOperationException(); } } // ========================================================================== // Accessors public Node getParent() { return parent; } /** * Gets the ancestor node relative to this. * * @param level

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> 0 = this, 1 = the parent, etc. */ public Node getAncestor(int level) { Preconditions.checkArgument(level >= 0); Node node = this; while (node != null && level-- > 0) { node = node.getParent(); } return node; } /** * Iterates all of the node's ancestors excluding itself. */ public AncestorIterable getAncestors() { return new AncestorIterable(this.getParent()); } /** * Iterator to go up the ancestor tree. */ public static class AncestorIterable implements Iterable<Node> { private Node cur; /** * @param cur The node to start. */ AncestorIterable(Node cur) { this.cur = cur; } public Iterator<Node> iterator() { return new Iterator<Node>() { public boolean hasNext() { return cur != null; } public Node next() { if (!hasNext()) throw new NoSuchElementException(); Node n = cur; cur = cur.getParent(); return n; } public void remove() { throw new UnsupportedOperationException(); } }; } } /** * Check for one child more efficiently than by iterating over all the * children as is done with Node.getChildCount(). * * @return Whether the node has exactly one child. */ public boolean hasOneChild() { return first != null && first == last; } /** * Check for more than one child more efficiently than by iterating over all * the children as is done with Node.getChildCount(). * * @return Whether the node more than one child. */ public boolean hasMoreThanOneChild() { return first != null && first != last; } public int getChildCount() { int c = 0; for (Node n = first; n != null; n = n.next) c++; return c; } // Intended for testing and verification only. public boolean hasChild(Node child) { for (Node n = first; n != null; n = n.getNext()) { if (child == n) { return true; } } return false; } /** * Checks if the subtree under this node is the same as another subtree. * Returns null if it's equal, or a message describing the differences. */ public String checkTreeEquals(Node node2) { NodeMismatch diff = checkTreeEqualsImpl(node2); if (diff != null) { return "Node tree inequality:" + "\nTree1:\n" + toStringTree() + "\n\nTree2:\n" + node2.toStringTree() + "\n\nSubtree1: " + diff.nodeA.toStringTree() + "\n\nSubtree2: " + diff.nodeB.toStringTree(); } return null; } /** * If this is a compilation pass and not a test, do not construct error * strings. Instead return true if the trees are equal. */ public boolean checkTreeEqualsSilent(Node node2) { return checkTreeEqualsImpl(node2) == null; } /** * Helper function to ignore differences in Node subclasses that are no longer * used. */ @

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>(String fileLevelComment) { JSDocInfo jsDocInfo = getJSDocInfo(); if (jsDocInfo == null) { // TODO(user): Is there a way to determine whether to // parse the JsDoc documentation from here? jsDocInfo = new JSDocInfo(false); } String license = jsDocInfo.getLicense(); if (license == null) { license = ""; } jsDocInfo.setLicense(license + fileLevelComment); setJSDocInfo(jsDocInfo); } } /** * Get the {@link JSDocInfo} attached to this node. * @return the information or {@code null} if no JSDoc is attached to this * node */ public JSDocInfo getJSDocInfo() { return (JSDocInfo) getProp(JSDOC_INFO_PROP); } /** * Sets the {@link JSDocInfo} attached to this node. */ public void setJSDocInfo(JSDocInfo info) { putProp(JSDOC_INFO_PROP, info); } /** * Sets whether this node is a variable length argument node. This * method is meaningful only on {@link Token#NAME} nodes * used to define a {@link Token#FUNCTION}'s argument list. */ public void setVarArgs(boolean varArgs) { putBooleanProp(VAR_ARGS_NAME, varArgs); } /** * Returns whether this node is a variable length argument node. This * method's return value is meaningful only on {@link Token#NAME} nodes * used to define a {@link Token#FUNCTION}'s argument list. */ public boolean isVarArgs() { return getBooleanProp(VAR_ARGS_NAME); } /** * Sets whether this node is an optional argument node. This * method is meaningful only on {@link Token#NAME} nodes * used to define a {@link Token#FUNCTION}'s argument list. */ public void setOptionalArg(boolean optionalArg) { putBooleanProp(OPT_ARG_NAME, optionalArg); } /** * Returns whether this node is an optional argument node. This * method's return value is meaningful only on {@link Token#NAME} nodes * used to define a {@link Token#FUNCTION}'s argument list. */ public boolean isOptionalArg() { return getBooleanProp(OPT_ARG_NAME); } /** * Sets whether this is a synthetic block that should not be considered * a real source block. */ public void setIsSyntheticBlock(boolean val) { putBooleanProp(SYNTHETIC_BLOCK_PROP, val); } /** * Returns whether this is a synthetic block that should not be considered * a real source block. */ public boolean isSyntheticBlock() { return getBooleanProp(SYNTHETIC_BLOCK_PROP); } /** * Sets the ES5 directives on this node. */ public void setDirectives(Set<String> val) { putProp(DIRECTIVES, val); } /** * Returns the set of ES5 directives for this node. */ @SuppressWarnings("unchecked") public Set<String> getDirectives() { return (Set<String>) getProp(DIRECTIVES);

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> } /** * Adds a warning to be suppressed. This is indistinguishable * from having a {@code @suppress} tag in the code. */ public void addSuppression(String warning) { if (getJSDocInfo() == null) { setJSDocInfo(new JSDocInfo(false)); } getJSDocInfo().addSuppression(warning); } /** * Sets whether this is a synthetic block that should not be considered * a real source block. */ public void setWasEmptyNode(boolean val) { putBooleanProp(EMPTY_BLOCK, val); } /** * Returns whether this is a synthetic block that should not be considered * a real source block. */ public boolean wasEmptyNode() { return getBooleanProp(EMPTY_BLOCK); } // There are four values of interest: // global state changes // this state changes // arguments state changes // whether the call throws an exception // locality of the result // We want a value of 0 to mean "global state changes and // unknown locality of result". final public static int FLAG_GLOBAL_STATE_UNMODIFIED = 1; final public static int FLAG_THIS_UNMODIFIED = 2; final public static int FLAG_ARGUMENTS_UNMODIFIED = 4; final public static int FLAG_NO_THROWS = 8; final public static int FLAG_LOCAL_RESULTS = 16; final public static int SIDE_EFFECTS_FLAGS_MASK = 31; final public static int SIDE_EFFECTS_ALL = 0; final public static int NO_SIDE_EFFECTS = FLAG_GLOBAL_STATE_UNMODIFIED | FLAG_THIS_UNMODIFIED | FLAG_ARGUMENTS_UNMODIFIED | FLAG_NO_THROWS; /** * Marks this function or constructor call's side effect flags. * This property is only meaningful for {@link Token#CALL} and * {@link Token#NEW} nodes. */ public void setSideEffectFlags(int flags) { Preconditions.checkArgument( getType() == Token.CALL || getType() == Token.NEW, "setIsNoSideEffectsCall only supports CALL and NEW nodes, got " + Token.name(getType())); putIntProp(SIDE_EFFECT_FLAGS, flags); } public void setSideEffectFlags(SideEffectFlags flags) { setSideEffectFlags(flags.valueOf()); } /** * Returns the side effects flags for this node. */ public int getSideEffectFlags() { return getIntProp(SIDE_EFFECT_FLAGS); } /** * A helper class for getting and setting the side-effect flags. * @author johnlenz@google.com (John Lenz) */ public static class SideEffectFlags { private int value = Node.SIDE_EFFECTS_ALL; public SideEffectFlags() { } public SideEffectFlags(int value) { this.value = value; } public int valueOf() { return value; } /** All side-effect occur and the returned results are non-local. */ public void setAllFlags() { value = Node.SIDE_EFFECTS_ALL; } /** No

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>/* * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Bob Jervis * Google Inc. * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package com.google.javascript.rhino; import com.google.javascript.rhino.JSDocInfo.Visibility; import java.util.Set; /** * A builder for {@link JSDocInfo} objects. This builder abstracts the * construction process of {@link JSDocInfo} objects whilst minimizing the * number of instances of {@link JSDocInfo} objects. It provides early * incompatibility detection among properties stored on the {@code JSDocInfo} * object being created. * */ final public class JSDocInfoBuilder { // the current JSDoc which is being populated private JSDocInfo currentInfo; // whether the current JSDocInfo has valuable information private boolean populated = false; // whether to include the documentation itself when parsing the JsDoc private boolean parseDocumentation = false; // the current marker, if any. private JSDocInfo.Marker currentMarker = null; public JSDocInfoBuilder(boolean parseDocumentation) { this.currentInfo = new JSDocInfo(parseDocumentation); this.parseDocumentation = parseDocumentation; } public boolean shouldParseDocumentation() { return parseDocumentation; } /** * Returns whether this builder is populated with information that can be * used to {@link #build} a {@link JSDocInfo} object. */ public boolean isPopulated() { return populated; } /** * Returns whether this builder is populated with information that can be * used to {@

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>link #build} a {@link JSDocInfo} object that has a * fileoverview tag. */ public boolean isPopulatedWithFileOverview() { return isPopulated() && (currentInfo.hasFileOverview() || currentInfo.isExterns() || currentInfo.isNoCompile()); } /** * Returns whether this builder recorded a description. */ public boolean isDescriptionRecorded() { return currentInfo.getDescription() != null; } /** * Builds a {@link JSDocInfo} object based on the populated information and * returns it. Once this method is called, the builder can be reused to build * another {@link JSDocInfo} object. * * @param sourceName The source file containing the JSDoc. * @return a {@link JSDocInfo} object populated with the values given to this * builder. If no value was populated, this method simply returns * {@code null} */ public JSDocInfo build(String sourceName) { if (populated) { JSDocInfo built = currentInfo; built.setSourceName(sourceName); populateDefaults(built); populated = false; currentInfo = new JSDocInfo(this.parseDocumentation); return built; } else { return null; } } /** Generate defaults when certain parameters are not specified. */ private static void populateDefaults(JSDocInfo info) { if (info.getVisibility() == null) { info.setVisibility(Visibility.INHERITED); } } /** * Adds a marker to the current JSDocInfo and populates the marker with the * annotation information. */ public void markAnnotation(String annotation, int lineno, int charno) { JSDocInfo.Marker marker = currentInfo.addMarker(); if (marker != null) { marker.annotation = new JSDocInfo.StringPosition(); marker.annotation.setItem(annotation); marker.annotation.setPositionInformation(lineno, charno, lineno, charno + annotation.length()); } currentMarker = marker; } /** * Adds a textual block to the current marker. */ public void markText(String text, int startLineno, int startCharno, int endLineno, int endCharno) { if (currentMarker != null) { currentMarker.description = new JSDocInfo.StringPosition(); currentMarker.description.setItem(text); currentMarker.description.setPositionInformation(startLineno, startCharno, endLineno, endCharno); } } /** * Adds a type declaration to the current marker. */ public void markTypeNode(Node typeNode, int lineno, int startCharno, int endCharno, boolean hasLC) { if (currentMarker != null) { currentMarker.type = new JSDocInfo.TypePosition(); currentMarker.type.setItem(typeNode); currentMarker.type.hasBrackets = hasLC; currentMarker.type.setPositionInformation(lineno, startCharno, lineno, endCharno); } } /** * Adds a name declaration to the current marker. */ public void markName(String name, int lineno, int charno) { if (

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>currentMarker != null) { currentMarker.name = new JSDocInfo.StringPosition(); currentMarker.name.setItem(name); currentMarker.name.setPositionInformation(lineno, charno, lineno, charno + name.length()); } } /** * Records a block-level description. * * @return {@code true} if the description was recorded. */ public boolean recordBlockDescription(String description) { populated = true; return currentInfo.documentBlock(description); } /** * Records a visibility. * * @return {@code true} if the visibility was recorded and {@code false} * if it was already defined */ public boolean recordVisibility(Visibility visibility) { if (currentInfo.getVisibility() == null) { populated = true; currentInfo.setVisibility(visibility); return true; } else { return false; } } /** * Records a typed parameter. * * @return {@code true} if the typed parameter was recorded and * {@code false} if a parameter with the same name was already defined */ public boolean recordParameter(String parameterName, JSTypeExpression type) { if (!hasAnySingletonTypeTags() && currentInfo.declareParam(type, parameterName)) { populated = true; return true; } else { return false; } } /** * Records a parameter's description. * * @return {@code true} if the parameter's description was recorded and * {@code false} if a parameter with the same name was already defined */ public boolean recordParameterDescription(String parameterName, String description) { if (currentInfo.documentParam(parameterName, description)) { populated = true; return true; } else { return false; } } /** * Records a template type name. * * @return {@code true} if the template type name was recorded and * {@code false} if a template type name was already defined. */ public boolean recordTemplateTypeName(String name) { if (currentInfo.declareTemplateTypeName(name)) { populated = true; return true; } else { return false; } } /** * Records a thrown type. */ public boolean recordThrowType(JSTypeExpression type) { if (!hasAnySingletonTypeTags()) { currentInfo.declareThrows(type); populated = true; return true; } return false; } /** * Records a throw type's description. * * @return {@code true} if the type's description was recorded and * {@code false} if a description with the same type was already defined */ public boolean recordThrowDescription(JSTypeExpression type, String description) { if (currentInfo.documentThrows(type, description)) { populated = true; return true; } else { return false; } } /** * Adds an author to the current information. */ public boolean addAuthor(String author) { if (currentInfo.documentAuthor(author)) { populated = true; return true; } else { return false; } } /** * Adds a reference ("@see") to the current information. */ public boolean addReference(String reference

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> currentInfo.setNoShadow(true); populated = true; return true; } else { return false; } } /** * Records that the {@link JSDocInfo} being built should have its * {@link JSDocInfo#isImplicitCast()} flag set to {@code true}. */ public boolean recordImplicitCast() { if (!currentInfo.isImplicitCast()) { currentInfo.setImplicitCast(true); populated = true; return true; } else { return false; } } /** * Records that the {@link JSDocInfo} being built should have its * {@link JSDocInfo#isNoSideEffects()} flag set to {@code true}. */ public boolean recordNoSideEffects() { if (!hasAnySingletonSideEffectTags() && !currentInfo.isNoSideEffects()) { currentInfo.setNoSideEffects(true); populated = true; return true; } else { return false; } } /** * Records that the {@link JSDocInfo} being built should have its * {@link JSDocInfo#isExterns()} flag set to {@code true}. */ public boolean recordExterns() { if (!currentInfo.isExterns()) { currentInfo.setExterns(true); populated = true; return true; } else { return false; } } /** * Whether the {@link JSDocInfo} being built will have its * {@link JSDocInfo#isInterface()} flag set to {@code true}. */ public boolean isInterfaceRecorded() { return currentInfo.isInterface(); } /** * @return Whether a parameter of the given name has already been recorded. */ public boolean hasParameter(String name) { return currentInfo.hasParameter(name); } /** * Records an implemented interface. */ public boolean recordImplementedInterface(JSTypeExpression interfaceName) { if (currentInfo.addImplementedInterface(interfaceName)) { populated = true; return true; } else { return false; } } /** * Records that we're lending to another name. */ public boolean recordLends(String name) { if (!hasAnyTypeRelatedTags()) { currentInfo.setLendsName(name); populated = true; return true; } else { return false; } } /** * Whether the current doc info has other type tags, like * {@code @param} or {@code @return} or {@code @type} or etc. */ private boolean hasAnyTypeRelatedTags() { return currentInfo.isConstructor() || currentInfo.isInterface() || currentInfo.getParameterCount() > 0 || currentInfo.hasReturnType() || currentInfo.hasBaseType() || currentInfo.getLendsName() != null || currentInfo.hasThisType() || hasAnySingletonTypeTags(); } /** * Whether the current doc info has any of the singleton type * tags that may not appear with other type tags, like * {@code @type} or {@code @typedef}. */ private boolean hasAnySingletonTypeTags() { return currentInfo.hasType() || currentInfo.hasType

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS> NOT_A_CLASS = "Not declared as a constructor"; public static final String NOT_A_TYPE = "Not declared as a type name"; public static final String EMPTY_TYPE_COMPONENT = "Named type with empty name component"; /** * Total ordering on types based on their textual representation. * This is used to have a deterministic output of the toString * method of the union type since this output is used in tests. */ static final Comparator<JSType> ALPHA = new Comparator<JSType>() { public int compare(JSType t1, JSType t2) { return t1.toString().compareTo(t2.toString()); } }; // A flag set on enum definition tree nodes public static final int ENUMDECL = 1; public static final int NOT_ENUMDECL = 0; final JSTypeRegistry registry; JSType(JSTypeRegistry registry) { this.registry = registry; } /** * Utility method for less verbose code. */ JSType getNativeType(JSTypeNative typeId) { return registry.getNativeType(typeId); } /** * Gets the docInfo for this type. By default, documentation cannot be * attached to arbitrary types. This must be overridden for * programmer-defined types. */ public JSDocInfo getJSDocInfo() { return null; } /** * If we see a type name without braces, it might be legacy jsdoc. * So we shouldn't emit warnings about it. This method is how we skip * those warnings. */ void forgiveUnknownNames() {} public boolean isNoType() { return false; } public boolean isNoObjectType() { return false; } public final boolean isEmptyType() { return isNoType() || isNoObjectType(); } public boolean isNumberObjectType() { return false; } public boolean isNumberValueType() { return false; } /** Whether this is the prototype of a function. */ public boolean isFunctionPrototypeType() { return false; } public boolean isStringObjectType() { return false; } boolean isTheObjectType() { return false; } public boolean isStringValueType() { return false; } /** * Tests whether the type is a string (value or Object). * @return {@code this &lt;: (String, string)} */ public final boolean isString() { return this.isSubtype( getNativeType(JSTypeNative.STRING_VALUE_OR_OBJECT_TYPE)); } /** * Tests whether the type is a number (value or Object). * @return {@code this &lt;: (Number, number)} */ public final boolean isNumber() { return this.isSubtype( getNativeType(JSTypeNative.NUMBER_VALUE_OR_OBJECT_TYPE)); } public boolean isArrayType() { return false; } public boolean isBooleanObjectType() { return false; } public boolean isBooleanValueType() { return false; } public boolean isRegexpType() { return false; } public boolean isDateType() { return false; } public boolean isNullType() { return false; } public boolean isVoidType() { return false;

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>/* * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Bob Jervis * Google Inc. * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package com.google.javascript.rhino; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import java.io.Serializable; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * <p>JSDoc information describing JavaScript code. JSDoc is represented as a * unified object with fields for each JSDoc annotation, even though some * combinations are incorrect. For instance, if a JSDoc describes an enum, * it cannot have information about a return type. This implementation * takes advantage of such incompatibilities to reuse fields for multiple * purposes, reducing memory consumption.</p> * * <p>Constructing {@link JSDocInfo} objects is simplified by * {@link JSDocInfoBuilder} which provides early incompatibility detection.</p> * */ public final class JSDocInfo implements Serializable { private static final long serialVersionUID = 1L; /** * Visibility categories. The {@link Visibility#ordinal()} can be used as a * numerical indicator of privacy, where 0 is the most private. This means * that the {@link Visibility#compareTo} method can be used to * determine if a visibility is more permissive than

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>10... private static final int TYPEFIELD_TYPE = 0x20000000; // 0010... private static final int TYPEFIELD_RETURN = 0x40000000; // 0100... private static final int TYPEFIELD_ENUM = 0x60000000; // 0110... private static final int TYPEFIELD_TYPEDEF = 0x80000000; // 1000... /** * Creates a {@link JSDocInfo} object. This object should be created using * a {@link JSDocInfoBuilder}. */ JSDocInfo(boolean includeDocumentation) { this.includeDocumentation = includeDocumentation; } // Visible for testing. public JSDocInfo() {} void setConstant(boolean value) { setFlag(value, MASK_CONSTANT); } void setConstructor(boolean value) { setFlag(value, MASK_CONSTRUCTOR); } void setDefine(boolean value) { setFlag(value, MASK_DEFINE); } void setHidden(boolean value) { setFlag(value, MASK_HIDDEN); } void setNoCheck(boolean value) { setFlag(value, MASK_NOCHECK); } void setShouldPreserveTry(boolean value) { setFlag(value, MASK_PRESERVETRY); } void setOverride(boolean value) { setFlag(value, MASK_OVERRIDE); } void setNoAlias(boolean value) { setFlag(value, MASK_NOALIAS); } // Visible for testing. public void setDeprecated(boolean value) { setFlag(value, MASK_DEPRECATED); } void setInterface(boolean value) { setFlag(value, MASK_INTERFACE); } void setExport(boolean value) { setFlag(value, MASK_EXPORT); } void setNoShadow(boolean value) { setFlag(value, MASK_NOSHADOW); } void setImplicitCast(boolean value) { setFlag(value, MASK_IMPLICITCAST); } void setNoSideEffects(boolean value) { setFlag(value, MASK_NOSIDEEFFECTS); } void setExterns(boolean value) { setFlag(value, MASK_EXTERNS); } void setJavaDispatch(boolean value) { setFlag(value, MASK_JAVADISPATCH); } void setNoCompile(boolean value) { setFlag(value, MASK_NOCOMPILE); } private void setFlag(boolean value, int mask) { if (value) { bitset |= mask; } else { bitset &= ~mask; } } /** * Returns whether the {@code @const} annotation is present on this * {@link JSDocInfo}. */ public boolean isConstant() { return getFlag(MASK_CONSTANT) || isDefine(); } /** * Returns whether the {@code @constructor} annotation is present on this * {@link JSDocInfo}. */ public boolean isConstructor() { return getFlag(MASK_CONSTRUCTOR); } /** * Returns whether the {@code @define} annotation

Closure, 91

<FILEB>
<CHANGES>
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends!= null &&
maybeLends.getLendsName()!= null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
<CHANGEE>
<FILEE>
<FILEB> } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING || pType == Token.NUMBER)) { return false; } // Don't traverse functions that are getting lent to a prototype. <CHANGES> <CHANGEE> } if (parent != null && parent.getType() == Token.ASSIGN) { Node lhs = parent.getFirstChild(); Node rhs = lhs.getNext(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { <FILEE> <SCANS>. */ public JSTypeExpression getThisType() { return thisType; } /** * Sets the type specified by the {@code @this} annotation. */ void setThisType(JSTypeExpression type) { this.thisType = type; } /** * Returns whether this {@link JSDocInfo} contains a type for {@code @this} * annotation. */ public boolean hasThisType() { return thisType != null; } void setBaseType(JSTypeExpression type) { lazyInitInfo(); info.baseType = type; } /** * Gets the base type specified by the {@code @extends} annotation. */ public JSTypeExpression getBaseType() { return (info == null) ? null : info.baseType; } /** * Gets the description specified by the {@code @desc} annotation. */ public String getDescription() { return (info == null) ? null : info.description; } void setDescription(String desc) { lazyInitInfo(); info.description = desc; } /** * Gets the meaning specified by the {@code @meaning} annotation. * * In localization systems, two messages with the same content but * different "meanings" may be translated differently. By default, we * use the name of the variable that the message is initialized to as * the "meaning" of the message. * * But some code generators (like Closure Templates) inject their own * meaning with the jsdoc {@code @meaning} annotation. */ public String getMeaning() { return (info == null) ? null : info.meaning; } void setMeaning(String meaning) { lazyInitInfo(); info.meaning = meaning; } /** * Gets the name we're lending to in a {@code @lends} annotation. * * In many reflection APIs, you pass an anonymous object to a function, * and that function mixes the anonymous object into another object. * The {@code @lends} annotation allows the type system to track * those property assignments. */ public String getLendsName() { return (info == null) ? null : info.lendsName; } void setLendsName(String name) { lazyInitInfo(); info.lendsName = name; } /** * Gets the description specified by the {@code @license} annotation. */ public String getLicense() { return (info == null) ? null : info.license; } /** License directives can appear in multiple comments, and always * apply to the entire file. Break protection and allow outsiders to * update the license string so that we can attach the license text even * when the JSDocInfo has been created and tagged with other information. * @param license String containing new license text. */ public void setLicense(String license) { lazyInitInfo(); info.license = license; } @Override public String toString() { return "JSDocInfo"; } /** * Returns whether this {@link JSDocInfo} contains a type for {@code @extends} * annotation. */ public boolean hasBaseType() { return getBaseType() != null; } /** *